1. 实现路由跳转
实现了与react-router-dom
的5.x
版本相同的用法,包括BrowserRouter
、Link
、Switch
、Route
等组件,基本用法如下代码所示:
<Router>
<div>
<Link to="/a">组件A</Link>
<Link to="/b">组件B</Link>
<Link to="/a/c">组件C</Link>
</div>
<Switch>
<Route path="/a" component={A} />
<Route path="/b" component={B} />
<Route path="/a/c" component={C} />
</Switch>
</Router>
复制代码
2. BrowserRouter
BrowserRouter
组件需要将全部的组件包起来,利用context
提供history
对象和location
对象。其中,history
对象由createBrowserHistory()
提供,location
对象初始值是window.location
。
history.listen()
用于监听location
的变化,当其发生变化时,更新state
中的location
。
import { createContext, useState, useEffect } from 'react';
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export const RouterContext = createContext();
const BrowserRouter = ({ children }) => {
const [location, setLocation] = useState(window.location);
useEffect(() => {
// 监听路由变化
const unlisten = history.listen(({ location: loc }) => {
setLocation(loc);
});
return () => {
unlisten && unlisten();
};
}, []);
return (
<RouterContext.Provider value={{ history, location }}>{children}</RouterContext.Provider>
);
};
export default BrowserRouter;
复制代码
3. Link
Link
组件里实际上是a
链接。但点击路由时,实际上是不发生跳转的,所以在onClick
事件里阻止了a
链接跳转的默认行为。并且利用history.push()
将当前的location
推入history
栈中,触发BrowserRouter
中的监听器,更新state
中的location
。此时window.location
也将更新,url
会发生变化。
import { useContext } from 'react';
import { RouterContext } from './BrowserRouter';
const style = {
border: '2px solid blue',
padding: '10px',
margin: '20px',
textDecoration: 'none',
};
const Link = ({ to, children }) => {
const { history } = useContext(RouterContext);
return (
<a
href={to}
style={style}
onClick={e => {
e.preventDefault();
history.push(to);
}}
>
{children}
</a>
);
};
export default Link;
复制代码
4. Switch
Switch
组件将一系列Route
组件包起来。若包含多个Route
组件,则children
是数组;若只有一个Route
,children
是单个元素,故进行判断,确保转化为数组。
将state
中location.pathname
与包含的Route
组件path
进行对比,只渲染匹配上的Route
。
import { useContext } from 'react';
import { RouterContext } from './BrowserRouter';
const Switch = ({ children }) => {
const { location } = useContext(RouterContext);
const routes = Array.isArray(children) ? children : [children];
return (
<>
{routes.map(child => {
const {
props: { path },
} = child;
if (location.pathname === path) return child;
return null;
})}
</>
);
};
export default Switch;
复制代码
5. Route
将state
中location.pathname
与当前Route
的path
进行对比,若匹配上,则渲染传入的组件。
import { createElement, useContext } from 'react';
import { RouterContext } from './BrowserRouter';
const Route = ({ path, component }) => {
const { location } = useContext(RouterContext);
return <>{location.pathname === path ? createElement(component) : null}</>;
};
export default Route;
复制代码
6. 源码
以上是本人学习所得之拙见,若有不妥,欢迎指出交流!