Appearance
dom 版本 Router
BrowserRouter
基于 Router 生成 Browser 类型的 BrowserRouter ,基于 createBrowserHistory 生成浏览器历史栈管理的 history 对象
- 创建 browser 类型的 history 实例对象
- 监听 history.listen 从而在浏览器路径切换的时候重新生成新的 location 和 action
ts
export function BrowserRouter({
basename,
children,
future,
window,
}: BrowserRouterProps) {
// 1. 创建 history 实例对象
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window, v5Compat: true });
}
let history = historyRef.current;
// 2. 生成url改变的时候 核心需要修改的对象 action 和 location
let [state, setStateImpl] = React.useState({
action: history.action,
location: history.location,
});
let { v7_startTransition } = future || {};
// 创建浏览器url切换回调函数 从而去更新当前history的 action 和 location 对象
// 使用了React18的新的API React.startTransition去回调,从而使得 history 的更新会非紧急,从而不打断核心的渲染流程
let setState = React.useCallback(
(newState: { action: NavigationType; location: Location }) => {
v7_startTransition && startTransitionImpl
? startTransitionImpl(() => setStateImpl(newState))
: setStateImpl(newState);
},
[setStateImpl, v7_startTransition]
);
// 2. 监听 history.listen 从而在浏览器路径切换的时候重新生成新的 location 和 action
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
future={future}
/>
);
}
react-router 的 Router
其实发现对于 Router 组件来说,其跟 history 对象很像,也是提供一个基础的组件给 BrowserRouter 或者 HashRouter 进行个性化定义;其核心是通过 React.Context 的模式给子孙组件提供 navigationContext 和 locationContext 两个上下文对象
- navigationContext
用来提供子孙组件中定义路由跳转需要的 方法 和 参数,其核心就是 navigator
属性,在 BrowserRouter 或者 HashRouter 也就是 history 对象。那么我们的子孙组件中 如 useNavigate()
就可以通过 let { basename, future, navigator } = React.useContext(NavigationContext);
获取原始的路由跳转方法
- locationContext
这个主要是提供子孙组件中访问当前 核心location对象
如 useLocation
ts
export function Router({
// 所有 URL 的基础位置,默认为 '/'
basename: basenameProp = "/",
children = null,
// location 实例对象
location: locationProp,
// navigation type
navigationType = NavigationType.Pop,
// history
navigator,
static: staticProp = false,
future,
}: RouterProps): React.ReactElement | null {
// Preserve trailing slashes on basename, so we can let the user control
// the enforcement of trailing slashes throughout the app
// 1. 生成 navigationContext 需要的数据
let basename = basenameProp.replace(/^\/*/, "/");
let navigationContext = React.useMemo(
() => ({
// 所有 URL 的基础位置,默认为 '/'
basename,
// 路由跳转方法
navigator,
static: staticProp,
future: {
v7_relativeSplatPath: false,
...future,
},
}),
[basename, future, navigator, staticProp]
);
// 2. 生成 locationContext 需要的数据
// 如果location是字符串类型的 就将其转换成 { pathname search hash }对象
if (typeof locationProp === "string") {
locationProp = parsePath(locationProp);
}
let {
pathname = "/",
search = "",
hash = "",
state = null,
key = "default",
} = locationProp;
let locationContext = React.useMemo(() => {
// 如果 pathname 中存在 basename 前缀 那么就移除basename 前缀
let trailingPathname = stripBasename(pathname, basename);
if (trailingPathname == null) {
return null;
}
return {
location: {
pathname: trailingPathname,
search,
hash,
state,
key,
},
navigationType,
};
}, [basename, pathname, search, hash, state, key, navigationType]);
if (locationContext == null) {
return null;
}
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider children={children} value={locationContext} />
</NavigationContext.Provider>
);
}