react-router
存在两中路由方式
- 数据路由
- 组件路由
数据路由是由 createBrowserRouter
、createHashRouter
、createMemoryRouter
等这些函数构造而成
使用 create*
这类的构造函数传入 routes 对象是一种嵌套类型的对象,这种嵌套反映了路由的父子关系去遍历生成 routes 类似于 useRoutes 对 routes 的处理
它会返回一个 route
的对象
router = {
get basename() {
return init.basename;
},
get state() {
return state;
},
get routes() {
return dataRoutes;
},
initialize,
subscribe,
enableScrollRestoration,
navigate,
fetch,
revalidate,
createHref: ,
getFetcher,
deleteFetcher,
dispose,
_internalFetchControllers: fetchControllers,
_internalActiveDeferreds: activeDeferreds,
};
这样使用
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";import ()
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
loader: () => {},
children: [
{
path: 'team',
element: <Team />,
loader: () => {}
}
]
}
])
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router}>
)
数据路由没有仔细看过这里,简单看一下组件路由
组件的使用方式
它会提供一个干净的路由将当前位置存储在浏览器的地址栏中,并使用浏览器内置的历史堆栈进行导航。
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
const root = createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
它的源码,比较简单,大致看一下
export function BrowserRouter({
basename,
children,
window,
}: BrowserRouterProps) {
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window, v5Compat: true });
}
let history = historyRef.current;
// 创建一个 state 状态
// 观察过 history 库可以明白 history.listen() 的参数会接收 {action, location}
let [state, setState] = React.useState({
action: history.action,
location: history.location,
});
// 在 dom 改变之前去往 history 添加 setState 事件,每次 history 更新都要添加
// 当 url 发生变化就会通过 history 库去触发 监听时间让整个组件更新
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
这里看一下 Router
的源码
export function Router({
basename: basenameProp = "/",
children = null,
location: locationProp,
navigationType = NavigationType.Pop,
navigator,
static: staticProp = false,
}: RouterProps): React.ReactElement | null {
invariant(
!useInRouterContext(),
`You cannot render a <Router> inside another <Router>.` +
` You should never have more than one in your app.`
);
// 比如:giao/* 会处理成 giao
let basename = basenameProp.replace(/^\/*/, "/");
// 存在 context 中
let navigationContext = React.useMemo(
() => ({ basename, navigator, static: staticProp }),
[basename, navigator, staticProp]
);
// location 如果是字符串就要解析成 对象
if (typeof locationProp === "string") {
// 将字符串解析为 pathname, search, hash 属性的对象
locationProp = parsePath(locationProp);
}
let {
pathname = "/",
search = "",
hash = "",
state = null,
key = "default",
} = locationProp;
// 构建一个当前的 location
let location = React.useMemo(() => {
// 这里的函数表明 basename 一定被包含于 pathname 才行
// 如果不是这样的关系就会返回 null ,如果 pathname === '/' 就直接返回
// 假如pathname: 'foo/name/www', basename: 'foo/' 那就会返回 foo/ 之后的 pathname 字符串
let trailingPathname = stripBasename(pathname, basename);
if (trailingPathname == null) {
return null;
}
return {
pathname: trailingPathname,
search,
hash,
state,
key,
};
}, [basename, pathname, search, hash, state, key]);
warning(
location != null,
`<Router basename="${basename}"> is not able to match the URL ` +
`"${pathname}${search}${hash}" because it does not start with the ` +
`basename, so the <Router> won't render anything.`
);
if (location == null) {
return null;
}
return (
// NavigationContext 是一个 context 存储 navigationContext
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location, navigationType }}
/>
</NavigationContext.Provider>
);
}
接下来使用 useRoutes
hook 来创建一些 匹配规则,匹配到响应的 url
渲染相应的组件
import { useRoutes } from 'react-router-dom'
const routes = {
path: '/',
element: <HomeLayout />,
children: [
{
path: '',
element: <Navigate to="spylist" />
},
// 实时信息
{
path: 'spylist',
element: <SpyList />
}
]
}
function App() {
return useRoutes(routes)
}
export default App