react-router之跳转

挑选和创建router
react-router共支持5种router类型,一个项目只支持使用一种router类型。

  • 在web项目里,常用的有两种historyRouter、hashRouter。
  • memoryRouter(单元测试用?或者使用DOM history APIs进行路由管理的项目使用?没有考证过)
  • nativeRouter(React Native项目使用)

react-router中创建router有两种方式

  • createBrowerRouter
  • <BrowserRouter>

两者的区别在于:

  • <BrowserRouter>只能将router配置使用jsx、tsx形式以children的形式传入。
  • createBrowerRouter可以使用RouterObject的形式,也可以使用官方提供的createRoutesFromElements方法,将jsx转换为object。另外,createBrowerRouter支持react-router的一些data apis,如:lazy、loader等。

// createRoutesFromElements const router = createBrowserRouter( createRoutesFromElements( <Route path="/" element={<Root />}> <Route path="dashboard" element={<Dashboard />} /> {/* ... etc. */} </Route> ) ); // jsx const router = createBrowserRouter([ { path: "/", element: <Root />, children: [ { path: "team", element: <Team />, }, ], }, ]);
createRoutesFromElements
// 使用递归处理route的层级结构,创建object对象,提取route里props,赋值为routeObject export function createRoutesFromChildren( children: React.ReactNode, parentPath: number[] = [] ): RouteObject[] { let routes: RouteObject[] = []; React.Children.forEach(children, (element, index) => { let treePath = [...parentPath, index]; if (element.type === React.Fragment) { // support React.Fragment routes.push.apply( routes, createRoutesFromChildren(element.props.children, treePath) ); return; } let route: RouteObject = { id: element.props.id || treePath.join("-"), caseSensitive: element.props.caseSensitive, element: element.props.element, Component: element.props.Component, index: element.props.index, path: element.props.path, loader: element.props.loader, action: element.props.action, errorElement: element.props.errorElement, ErrorBoundary: element.props.ErrorBoundary, hasErrorBoundary: element.props.ErrorBoundary != null || element.props.errorElement != null, shouldRevalidate: element.props.shouldRevalidate, handle: element.props.handle, lazy: element.props.lazy, }; if (element.props.children) { route.children = createRoutesFromChildren( element.props.children, treePath ); } routes.push(route); }); return routes; }
createBrowserRouter
web中常用的两种router,创建时都是调用的createRouter方法,执行initialize方法,区别是history的创建方法不同。
export function createBrowserRouter( routes: RouteObject[], opts?: DOMRouterOpts ): RemixRouter { return createRouter({ basename: opts?.basename, future: { ...opts?.future, v7_prependBasename: true, }, history: createBrowserHistory({ window: opts?.window }), hydrationData: opts?.hydrationData || parseHydrationData(), routes, mapRouteProperties, unstable_dataStrategy: opts?.unstable_dataStrategy, window: opts?.window, }).initialize(); } export function createHashRouter( routes: RouteObject[], opts?: DOMRouterOpts ): RemixRouter { return createRouter({ basename: opts?.basename, future: { ...opts?.future, v7_prependBasename: true, }, history: createHashHistory({ window: opts?.window }), hydrationData: opts?.hydrationData || parseHydrationData(), routes, mapRouteProperties, unstable_dataStrategy: opts?.unstable_dataStrategy, window: opts?.window, }).initialize(); }
createBrowserHistory
该方法会创建history对象,将listen、push、replace、handlePop这几个方法赋值给history对象,并返回
createRouter
代码有省略
/** 创建router对象,处理跳转 */ export function createRouter(init: RouterInit): Router { let mapRouteProperties: MapRoutePropertiesFunction; // 拍平routes结构 let dataRoutes = convertRoutesToDataRoutes( init.routes, mapRouteProperties, undefined, manifest ); let state: RouterState = {}; // state变化时,会触发set里的的回调函数 // 回调函数注册是通过subscribe方法 // function subscribe(fn: RouterSubscriber) { // subscribers.add(fn); // return () => subscribers.delete(fn); // } let subscribers = new Set<RouterSubscriber>(); // 匹配route let initialMatches = matchRoutes(dataRoutes, init.history.location, basename); let router: Router; // 初始化router的方法 // 调用方式为 let router = createRouter(init).initialize(); function initialize() { // 注册监听popstate,传入popstate事件监听回调函数,popstate事件触发的acion为POP unlistenHistory = init.history.listen( ({ action: historyAction, location, delta }) => { // 内部实际执行为startNavigation方法 return startNavigation(historyAction, location); } ); // 返回router对象 return router; } // 更新state并通知state变化 function updateState(newState: Partial<RouterState>,...): void { state = { ...state, ...newState, }; // 遍历subscribers,并执行内部存储的回调方法 [...subscribers].forEach((subscriber) => subscriber(state, {...}) ); } // 根据传入的location、action执行跳转 async function startNavigation( historyAction: HistoryAction, location: Location, opts?: {...} ): Promise<void> { pendingAction = historyAction; // 匹配路由 let matches = matchRoutes(routesToUse, location, basename); // 未匹配到,返回404错误error boundary if (!matches) { let error = getInternalRouterError(404, { pathname: location.pathname }); completeNavigation(...); return; } // 执行completeNavigation completeNavigation(location, { matches, ...getActionDataForCommit(pendingActionResult), loaderData, errors, }); } // 完成navigation、更新state(historyAction/location/matches) function completeNavigation(location,newState): void { // 根据跳转类型,执行不同方法 if (pendingAction === HistoryAction.Pop) { // popstate事件触发,不需要动作,因为事件触发说明,url已经发生了变更。 } else if (pendingAction === HistoryAction.Push) { // 此处即执行window.history.pushState方法 init.history.push(location, location.state); } else if (pendingAction === HistoryAction.Replace) { // 此处即执行window.history.replaceState方法 init.history.replace(location, location.state); } // 更新state updateState(...props); } // 手动触发跳转方法 --useNavigate内部即执行此方法 async function navigate(to: number | To | null,opts?: RouterNavigateOptions){ // 传入数字,即执行window.history.go方法 if (typeof to === "number") { init.history.go(to); return; } // 其他情况即执行startNavigation return await startNavigation(...props); } // 返回router对象 router = { initialize, subscribe, navigate, }; return router; }
RouterProvider
// RouterProvider通过context,将各种router相关的变量传递给下层组件 export function RouterProvider({ fallbackElement, router, future, }: RouterProviderProps): React.ReactElement { let [state, setStateImpl] = React.useState(router.state); // 执行updateState方法后会触发state的变更,从而触发重新渲染,渲染逻辑包含在DataRoutes中 let setState = React.useCallback<RouterSubscriber>( (newState: RouterState) => {setStateImpl(newState);}, [setStateImpl] ); // 注册updateState变化后的回调方法, React.useLayoutEffect(() => router.subscribe(setState), [router, setState]); let navigator = React.useMemo((): Navigator => { return { createHref: router.createHref, encodeLocation: router.encodeLocation, go: (n) => router.navigate(n), push: (to, state, opts) => router.navigate(to, { state, preventScrollReset: opts?.preventScrollReset, }), replace: (to, state, opts) => router.navigate(to, { replace: true, state, preventScrollReset: opts?.preventScrollReset, }), }; }, [router]); let basename = router.basename || "/"; let dataRouterContext = React.useMemo( () => ({ router, navigator, static: false, basename, }), [router, navigator, basename] ); return ( <> <DataRouterContext.Provider value={dataRouterContext}> <DataRouterStateContext.Provider value={state}> <Router basename={basename} location={state.location} navigationType={state.historyAction} navigator={navigator} > <DataRoutes routes={router.routes} future={router.future} state={state} /> </Router> </DataRouterStateContext.Provider> </DataRouterContext.Provider> {null} </> ); }
总结
与跳转有关的逻辑,router需要做的事情简单来说有两个,即管理url和根据url渲染相关的组件。 url的变更分为两种类型,


原文链接:https://juejin.cn/post/7377741132825116708
 

  • 外部触发的url变更(外部相对于react-router而言),此类变更是通过监听popState事件,执行handlePop方法,即startNavigation->completeNavigation方法来达到的
  • react-router内部的方法触发的,比如useNavigate hook、Navigate组件等,此类变更是通过主动触发startNavigation方法来达到的,与外部触发的区别在于,主动触发的跳转需要维护url,同时不能触发页面的刷新,这里的实现即通过原生的history.pushState和history.replaceState方法达到的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值