Skip to main content

react-router v6 源码解析

概要

react-router 依赖 history,了解 history 请看 history 源码解析

使用

路由结构

Router > Routes > Route

import { Routes , Route , Outlet, BrowserRouter } from 'react-router-dom'

const About = React.lazy(() => import("./pages/About"));

const App = () => (
<BrowserRouter>
<h3>Menu</h3>
<Routes>
<Route element={<Home />} path="/home" />
<Route
element={
<React.Suspense fallback={<span>Loading...</span>}>
<About />
</React.Suspense>
}
path="/about"
/>
<Route element={<Multi />} path="/multi">
<Route element={<PartOne />} path="/multi/p1" />
<Route element={<PartTwo />} path="/multi/p2" />
</Route>
</Routes>
</BrowserRouter>
)

const Multi = () => (
<div>
<h3>Multi </h3>
<Outlet />
</div>
)

Hooks

useNavigate() 路由跳转方法

useLocation() 获取当前路由数据

useParams() 获取动态路由的参数

useSearchParams() 获取 URLSearchParams 对象

Router(BrowserRouter)

路由组件之间通过 context 通信,而通信的源头 Provider 就是 Router。以 BrowserRouter 为例。

通过监听 history ,路由改变后触发 setState。

Router 组件提供了 Navigation 和 Location 的 Context provider,是所有 Routes、hooks 的数据来源。

import { createBrowserHistory } from 'history'

function BrowserRouter() {
let historyRef = React.useRef()
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window })
}

let history = historyRef.current
let [state, setState] = React.useState({
action: history.action,
location: history.location,
})

React.useLayoutEffect(() => history.listen(setState), [history])

return (
<Router {/** props... */} />
)
}

function Router () {
// 组装 navigation 对象
// 组装 location 对象
return (
<NavigationContext.Provider value={navigation}>
<LocationContext.Provider children={children} value={{ location, navigationType }} />
</NavigationContext.Provider>
)
}

Routes(useRoutes)

useRoutes

再说 Routes 之前,再了解一种创建路由结构的方式,那就是 useRoutes。直接传入这样的路由结构就可以啦,非常直观方便。

const routes = [
{
path: "/home",
element: <Home />,
},
{
path: "/multi",
element: <Multi />,
children: [
{ index: true, element: <MultiMain /> },
{ path: "/multi/p1", element: <MultiP1 /> },
{ path: "/multi/p2", element: <MultiP2 /> },
],
},
{ path: "*", element: <NoMatch /> }
]
const App = () => {
let element = useRoutes(routes)

return (
element
)
}

Routes

Routes 组件内部,实际上就是用的 useRoutes。通过 createRoutesFromChildren 方法收集 children 上的 props 组成类似于上面这样的结构的。

function Routes({ children, location }) {
return useRoutes(createRoutesFromChildren(children), location);
}

createRoutesFromChildren 就是递归的调用,并收集 props ,没啥好看的,主要看 useRoutes

function useRoutes() {
// ...
const matches = matcheRoutes()
// ...
return _renderMatches(matches)
}

matchRoutes

matchRoutes 是做了扁平化 routes,返回路由层级比如 / => /multi => /p1

_renderMatches

这块代码值得好好品,matches 从右往左的嵌套 outlet,最终形成有层级的路由布局。

Outlet 组件将会通过消费者拿到 outlet ,并渲染。

function _renderMatches(matches, parentMatches = []) {
if (matches == null) return null

return matches.reduceRight((outlet, match, index) => {
return (
<RouteContext.Provider
children={match.route.element !== undefined ? match.route.element : <Outlet />}
value={{
outlet,
matches: parentMatches.concat(matches.slice(0, index + 1)),
}}
/>
)
}, null)
}

Route

从来上面看出来 Route 都没有被使用过,

实际上 Route 就是一个空壳组件,存在的目的是为了开发者编写嵌套的路由结构, Routes通过 children 属性收集数据用的。