react-router 几种模式及实现原理
- 利用关键 api,替换路由组件,但是不触发页面刷新
1. 路由模式概述
React Router 主要提供三种路由模式:
| 模式 | 原理 | 适用场景 | URL 示例 |
|---|---|---|---|
| BrowserRouter | HTML5 History API | 现代浏览器单页应用 | https://example.com/about |
| HashRouter | URL Hash | 传统浏览器、静态部署 | https://example.com/#/about |
| MemoryRouter | 内存路由 | 测试、非浏览器环境 | 无 URL 变化 |
2. BrowserRouter 实现原理
核心:HTML5 History API
js
// History API 主要方法
window.history.pushState(state, title, url); // 添加历史记录
window.history.replaceState(state, title, url); // 替换当前历史记录
window.history.back(); // 后退
window.history.forward(); // 前进
window.history.go(n); // 跳转到指定历史记录
// popstate 事件监听浏览器前进后退
window.addEventListener("popstate", (event) => {
console.log("Location changed:", window.location.pathname);
});BrowserRouter 实现源码解析
js
class BrowserRouter extends React.Component {
constructor(props) {
super(props);
this.state = {
location: window.location,
};
// 监听 popstate 事件(浏览器前进后退)
this.unlisten = history.listen((location) => {
this.setState({ location });
});
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<Router
location={this.state.location}
navigator={history}
navigationType={this.state.action}
>
{this.props.children}
</Router>
);
}
}创建自定义 History 对象
js
// 自定义 history 实现
function createBrowserHistory() {
const globalHistory = window.history;
let listeners = [];
let action = "POP";
let state;
function push(path, state) {
action = "PUSH";
const location = createLocation(path, state);
globalHistory.pushState(state, "", path);
notifyListeners(location);
}
function replace(path, state) {
action = "REPLACE";
const location = createLocation(path, state);
globalHistory.replaceState(state, "", path);
notifyListeners(location);
}
function go(n) {
globalHistory.go(n);
}
function listen(listener) {
listeners.push(listener);
// 返回取消监听函数
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
function notifyListeners(location) {
listeners.forEach((listener) => listener(location));
}
// 处理浏览器前进后退
window.addEventListener("popstate", () => {
action = "POP";
const location = createLocation(window.location.pathname);
notifyListeners(location);
});
return { push, replace, go, listen };
}3. HashRouter 实现原理
核心:URL Hash 变化
js
// Hash 变化的监听
window.addEventListener("hashchange", () => {
console.log("Hash changed:", window.location.hash);
});
// 手动修改 hash
window.location.hash = "#/about"; // 会触发 hashchangeHashRouter 实现源码解析
js
class HashRouter extends React.Component {
constructor(props) {
super(props);
this.state = {
location: {
pathname: window.location.hash.slice(1) || "/",
search: window.location.search,
hash: window.location.hash,
state: null,
},
};
// 监听 hashchange 事件
this.hashChangeHandler = () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || "/",
},
});
};
window.addEventListener("hashchange", this.hashChangeHandler);
}
componentWillUnmount() {
window.removeEventListener("hashchange", this.hashChangeHandler);
}
navigate = (to, state) => {
window.location.hash = to;
};
render() {
return (
<Router
location={this.state.location}
navigator={{
push: this.navigate,
replace: (to) => {
// replace 实现较复杂,需要操作历史记录
const hash = to.startsWith("#") ? to : `#${to}`;
window.location.replace(window.location.pathname + hash);
},
}}
>
{this.props.children}
</Router>
);
}
}4. MemoryRouter 实现原理
核心:内存中管理路由状态
js
class MemoryRouter extends React.Component {
constructor(props) {
super(props);
this.navigate = this.navigate.bind(this);
this.history = {
entries: [{ pathname: props.initialEntries?.[0] || "/" }],
index: 0,
push: (to) => this.navigate(to, "PUSH"),
replace: (to) => this.navigate(to, "REPLACE"),
go: (n) => {
const newIndex = this.history.index + n;
if (newIndex >= 0 && newIndex < this.history.entries.length) {
this.history.index = newIndex;
this.setState({ location: this.history.entries[newIndex] });
}
},
};
this.state = {
location: this.history.entries[this.history.index],
};
}
navigate(to, action) {
const location = typeof to === "string" ? { pathname: to } : to;
if (action === "PUSH") {
this.history.entries = this.history.entries.slice(
0,
this.history.index + 1
);
this.history.entries.push(location);
this.history.index = this.history.entries.length - 1;
} else if (action === "REPLACE") {
this.history.entries[this.history.index] = location;
}
this.setState({ location });
}
render() {
return (
<Router
location={this.state.location}
navigator={this.history}
navigationType={this.state.action}
>
{this.props.children}
</Router>
);
}
}5. Router 核心组件实现
基础 Router 组件
js
const NavigationContext = React.createContext();
const LocationContext = React.createContext();
class Router extends React.Component {
constructor(props) {
super(props);
this.state = {
location: props.location,
action: props.navigationType || "POP",
};
// 监听导航变化
this.unlisten = props.navigator.listen((location, action) => {
this.setState({ location, action });
});
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<NavigationContext.Provider value={this.props.navigator}>
<LocationContext.Provider value={this.state}>
{this.props.children}
</LocationContext.Provider>
</NavigationContext.Provider>
);
}
}6. 路由匹配原理
Route 组件实现
js
function Route({ path, element, children }) {
const { location } = useContext(LocationContext);
const match = matchPath(path, location.pathname);
if (!match) return null;
return React.cloneElement(element, {
match,
location,
params: match.params,
});
}
// 路径匹配算法
function matchPath(pattern, pathname) {
const keys = [];
const regex = pathToRegexp(pattern, keys);
const match = regex.exec(pathname);
if (!match) return null;
const params = {};
keys.forEach((key, index) => {
params[key.name] = match[index + 1];
});
return {
params,
path: pattern,
url: pathname,
isExact: match[0] === pathname,
};
}
// 路径转正则表达式(简化版)
function pathToRegexp(path, keys) {
const pattern = path
.replace(/:(\w+)/g, (_, key) => {
keys.push({ name: key });
return "([^/]+)";
})
.replace(/\*/g, "(.*)");
return new RegExp(`^${pattern}$`);
}7. 导航组件实现
Link 组件原理
js
function Link({ to, children, ...props }) {
const navigator = useContext(NavigationContext);
const handleClick = (event) => {
event.preventDefault();
navigator.push(to);
};
return (
<a href={to} onClick={handleClick} {...props}>
{children}
</a>
);
}
// NavLink 增强版(支持 active 状态)
function NavLink({
to,
style,
className,
activeStyle,
activeClassName,
...props
}) {
const { location } = useContext(LocationContext);
const isActive = location.pathname === to;
const linkStyle = isActive ? { ...style, ...activeStyle } : style;
const linkClassName = isActive
? `${className} ${activeClassName}`.trim()
: className;
return (
<Link to={to} style={linkStyle} className={linkClassName} {...props} />
);
}8. 编程式导航
useNavigate Hook 实现
js
function useNavigate() {
const navigator = useContext(NavigationContext);
const { location } = useContext(LocationContext);
return function navigate(to, options = {}) {
if (options.replace) {
navigator.replace(to, options.state);
} else {
navigator.push(to, options.state);
}
};
}
// useLocation Hook
function useLocation() {
return useContext(LocationContext).location;
}
// useParams Hook
function useParams() {
const { match } = useContext(RouteContext);
return match ? match.params : {};
}9. 路由守卫实现
自定义路由保护
js
function ProtectedRoute({ children, requiredRole }) {
const location = useLocation();
const user = useUser(); // 自定义 hook 获取用户信息
if (!user.isAuthenticated) {
// 重定向到登录页
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (requiredRole && user.role !== requiredRole) {
// 权限不足
return <div>Access Denied</div>;
}
return children;
}
// 使用示例
<Route
path="/admin"
element={
<ProtectedRoute requiredRole="admin">
<AdminPage />
</ProtectedRoute>
}
/>;10. 性能优化
路由懒加载
js
import { lazy, Suspense } from "react";
const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}