# react-router **Repository Path**: lzy_8920/react-router ## Basic Information - **Project Name**: react-router - **Description**: react-router-v6 react路由相关内容 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-02-26 - **Last Updated**: 2023-08-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # React Router ## 1. React Router使用用法 React Router官网地址:https://reactrouter.com/ React Router中文Gitbook:https://react-guide.github.io/react-router-cn/ ### 1.1 React Router 功能介绍 React Router 是React生态库之一,是可以在CSR和SSR环境下,为了React而设计的路由库 - 客户端:React环境 - 服务端:node、RN 以最新版V6为例: ### 1.2 安装介绍 - npx create react app XXX yarn add react-router-dom@6 ```JS // src/index.js import * as React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; const root = ReactDOM.createRoot( document.getElementById("root") ); root.render( ); // src/App.js import * as React from "react"; import { Routes, Route, Link } from "react-router-dom"; import "./App.css"; function App() { return (

Welcome to React Router!

} /> } />
); } // App.js function Home() { return ( <>

Welcome to the homepage!

You can do this, I believe in you.

); } function About() { return ( <>

Who are we?

That feels like an existential question, don't you think?

); } ``` - webpack 安装 - HTML Script 安装 ### 1.3 基本用法 #### 1.3.1 配置路由 ```JS import ReactDOM from "react-dom/client"; import { BrowserRouter, Routes, Route, } from "react-router-dom"; // import your route components too const root = ReactDOM.createRoot( document.getElementById("root") ); root.render( }> } /> }> } /> } /> } /> ); ``` #### 1.3.2 路由匹配 在先前版本的React Router中,针对多个匹配到的router,我们需要声明出具体的匹配逻辑,但V6相对更“智能” teams/111:匹配 teams/new:匹配下面的 ```JS } /> } /> ``` #### 1.3.3 路由跳转 Navigation / Link ```JS // Link import { Link } from "react-router-dom"; function Home() { return (

Home

); } // useNavigate,更多用于JS操作后跳转使用 import { useNavigate } from "react-router-dom"; function Invoices() { let navigate = useNavigate(); return (
{ let newInvoice = await createInvoice( event.target ); navigate(`/invoices/${newInvoice.id}`); }} />
); } ``` #### 1.3.4 获取路由参数 使用url的路径参数,常用于匹配path 参数后的id ```JS import { Routes, Route, useParams } from "react-router-dom"; function App() { return ( } /> ); } function Invoice() { let params = useParams(); return

Invoice {params.invoiceId}

; } // example function Invoice() { let { invoiceId } = useParams(); let invoice = useFakeFetch(`/api/invoices/${invoiceId}`); return invoice ? (

{invoice.customerName}

) : ( ); } ``` #### 1.3.5 嵌套路由 - 路由路径 匹配 url路径 ```JS function App() { return ( }> } /> } /> ); } // /invoices/sent // /invoices/123 ``` - Outlet: 父router中子router可以用表示 ```JS // 父router中子router可以用表示 import { Routes, Route, Outlet } from "react-router-dom"; function App() { return ( }> } /> } /> ); } function Invoices() { return (

Invoices

// 匹配对应的 或者
); } function Invoice() { let { invoiceId } = useParams(); return

Invoice {invoiceId}

; } function SentInvoices() { return

Sent Invoices

; } // 在跟router中添加Link 跳转 import { Routes, Route, Link, Outlet, } from "react-router-dom"; function App() { return ( }> } /> } /> ); } function Layout() { return (

Welcome to the app!

); } function Invoices() { return

Invoices

; } function Dashboard() { return

Dashboard

; } ``` #### 1.3.6 index routes ```JS function App() { return ( }> } /> } /> } /> ); } function Layout() { return (
); } // 如果是 "/" ``` #### 1.3.7 兜底 routes ```JS function App() { return ( } /> } /> } /> ); } ``` #### 1.3.8 多个routes集成在一个组件 ```JS function App() { return (
} /> } /> }> } /> } /> }> } /> } /> } />
); } ``` #### 1.3.9 后代中使用Routes ```JS function App() { return ( } /> } /> ); } function Dashboard() { return (

Look, more routes!

} /> // dashboard } /> // /dashboard/invoices
); } ``` #### 1.3.10 useRoutes ```JS import { useRoutes } from 'react-router-dom'; const SetRoutes = () => { const routes = useRoutes([ { path:'/', element:
...
}, { path:'/a', element:
aaa
} ]); return ( {routes} ) } ``` ## 2. 升级到V6的一些问题 ### 2.1 删除withRouter - withRouter用处 将一个组件包裹进Route里面, 然后react-router的三个对象history, location, match就会被放进这个组件的props属性中,可以实现对应的功能 ```JS import React from 'react' import './nav.css' import { NavLink, withRouter } from "react-router-dom" class Nav extends React.Component{ handleClick = () => { // Route 的 三个对象将会被放进来, 对象里面的方法可以被调用 console.log(this.props); } render() { return (
掘土社区
  • 首页
  • 动态
  • 话题
  • 登录
  • ); } } // 导出的是 withRouter(Nav) 函数执行 export default withRouter(Nav) ``` - React Router的V6中,更多的使用hooks语法,而withRouter的用法更多的用在Class组件里,只要可以将类组件转为函数组件即可 ```JS import { useLocation, useNavigate, useParams, } from "react-router-dom"; function withRouter(Component) { function ComponentWithRouterProp(props) { let location = useLocation(); let navigate = useNavigate(); let params = useParams(); return ( ); } return ComponentWithRouterProp; } ``` ### 2.2 在V6以下的版本里,支持 Route component 和 Route render,V6中只支持 Route element - 参考React 中Suspense的用法,}>,传入的是React 元素,而非组件,可以将props更容易的传入到对应的元素内。 - V6以下形式的包版本体积过大 ```JS // V6以下 ( )} /> ( match ? ( ) : ( ) )} /> // V6 } /> } /> function Profile({ animate }) { // 使用hooks,在元素定义内处理逻辑 let params = useParams(); let location = useLocation(); } ``` ### 2.3 取消正则路由 - 正则路由为V6版本的路由排序带来很多问题,比如,如果定义一个正则的优先级? - 正则路由占据了React Router近1/3的体积 - 正则路由能表达的,V6版本都支持 ```JS // V5 function App() { return ( ); } function Lang({ params }) { let lang = params[0]; let translations = I81n[lang]; // ... } // V6 function App() { return ( } /> } /> } /> ); } function Lang({ lang }) { let translations = I81n[lang]; // ... } // V5 function App() { return ( ); } function User({ params }) { let id = params[0]; // ... } // V6 function App() { return ( } /> } /> ); } function ValidateUser() { let params = useParams(); let userId = params.id.match(/\d+/); if (!userId) { return ; } return ; } function User(props) { let id = props.id; // ... } ``` ## 3. 核心实现&源码解析 ### 3.1 BrowserRouter - 确定是web运行环境,利用工具方法 createBrowserHistory 创建了对 Window.history API的自定义封装实例。 - 同时向自定义history实例上注册监听器,当路由发生变化时,会回调执行setState方法更新action和location信息,然后触发组件的更新和重新渲染。 ```JS export function BrowserRouter({ basename, children, window, }: BrowserRouterProps) { let historyRef = React.useRef(); if (historyRef.current == null) { // 创建自定义封装实例 historyRef.current = createBrowserHistory({ window, v5Compat: true }); } let history = historyRef.current; let [state, setState] = React.useState({ action: history.action, // pop push replace location: history.location, // 对window.location 对象的映射包装 }); // 在histroy上注册观察者,当路由发生变化时,执行setState 触发更新渲染 React.useLayoutEffect(() => history.listen(setState), [history]); return ( ); } ``` ### 3.2 useRoutes/Routes - useRoutes功能上等同于 ``,但它使用 JS 对象而不是 `` 元素来定义路由 - `useRoutes` 的返回值是可用于呈现路由树的有效 React 元素,或因无匹配路由返回 `null` ```JS export function useRoutes( routes: RouteObject[], locationArg?: Partial | string ): React.ReactElement | null { invariant( useInRouterContext(), `useRoutes() may be used only in the context of a component.` ); let { navigator } = React.useContext(NavigationContext); let dataRouterStateContext = React.useContext(DataRouterStateContext); let { matches: parentMatches } = React.useContext(RouteContext); let routeMatch = parentMatches[parentMatches.length - 1]; let parentParams = routeMatch ? routeMatch.params : {}; let parentPathname = routeMatch ? routeMatch.pathname : "/"; let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/"; let parentRoute = routeMatch && routeMatch.route; if (__DEV__) { let parentPath = (parentRoute && parentRoute.path) || ""; warningOnce( parentPathname, !parentRoute || parentPath.endsWith("*"), `You rendered descendant (or called \`useRoutes()\`) at ` + `"${parentPathname}" (under ) but the ` + `parent route path has no trailing "*". This means if you navigate ` + `deeper, the parent won't match anymore and therefore the child ` + `routes will never render.\n\n` + `Please change the parent to .` ); } let locationFromContext = useLocation(); let location; if (locationArg) { let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg; invariant( parentPathnameBase === "/" || parsedLocationArg.pathname?.startsWith(parentPathnameBase), `When overriding the location using \`\` or \`useRoutes(routes, location)\`, ` + `the location pathname must begin with the portion of the URL pathname that was ` + `matched by all parent routes. The current pathname base is "${parentPathnameBase}" ` + `but pathname "${parsedLocationArg.pathname}" was given in the \`location\` prop.` ); location = parsedLocationArg; } else { location = locationFromContext; } let pathname = location.pathname || "/"; let remainingPathname = parentPathnameBase === "/" ? pathname : pathname.slice(parentPathnameBase.length) || "/"; // 通过传入的routes配置项与当前路径,匹配对应渲染的路由 let matches = matchRoutes(routes, { pathname: remainingPathname }); // 返回的是react.element,渲染所有match到的对象 let renderedMatches = _renderMatches( matches && matches.map((match) => Object.assign({}, match, { params: Object.assign({}, parentParams, match.params), // joinPaths 用于合并字符串 pathname: joinPaths([ parentPathnameBase, // Re-encode pathnames that were decoded inside matchRoutes navigator.encodeLocation ? navigator.encodeLocation(match.pathname).pathname : match.pathname, ]), pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([ parentPathnameBase, // Re-encode pathnames that were decoded inside matchRoutes navigator.encodeLocation ? navigator.encodeLocation(match.pathnameBase).pathname : match.pathnameBase, ]), }) ), // 外层的parentMatches 部分,最后会一起加入最终 matches 参数中 parentMatches, dataRouterStateContext || undefined ); if (locationArg && renderedMatches) { return ( {renderedMatches} ); } return renderedMatches; } ``` #### 3.2.1 路由匹配(matchRoutes) - flattenRoutes 路由扁平化 - rankRouteBranches 路由排序 比较权重 - matchRouteBranch 路由匹配 ```JS /** * 通过 routes 与 location 得到 matches 数组 */ export function matchRoutes( // 用户传入的 routes 对象 routes: RouteObject[], locationArg: Partial | string, basename = "/" ): RouteMatch[] | null { let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg; let pathname = stripBasename(location.pathname || "/", basename); // basename 匹配失败,返回 null if (pathname == null) { return null; } // 1.扁平化 routes,将树状的 routes 对象根据 path 扁平为一维数组 let branches = flattenRoutes(routes); // 2.传入扁平化后的数组,根据内部匹配到的权重排序 rankRouteBranches(branches); let matches = null; // 3.这里就是权重比较完成后的解析顺序,权重高的在前面,先进行匹配,然后是权重低的匹配 // branches 中有一个匹配到了就终止循环,或者全都没有匹配到 for (let i = 0; matches == null && i < branches.length; ++i) { // 遍历扁平化的 routes,查看每个 branch 的路径匹配规则是否能匹配到 pathname matches = matchRouteBranch(branches[i], pathname); } return matches; } ``` #### 3.2.2 路由渲染(_renderMatches) _renderMatches将matchRoutes的结果渲染成React元素 - 这个函数为每个匹配组路由组(嵌套路由)建立 `RouteContext`,children 即为需要渲染的 React 元素 - 利用 `reduceRight()` 方法,从右往左开始遍历,也就是从子到父的嵌套路由顺序,将前一项的 React 节点作为下一个 React 节点的 `outlet` ```JS /** * 其实就是渲染 RouteContext.Provider 组件(包括多个嵌套的 Provider) */ function _renderMatches( matches: RouteMatch[] | null, // 如果在已有 match 的 route 内部调用,会合并父 context 的 match parentMatches: RouteMatch[] = [] ): React.ReactElement | null { if (matches == null) return null; // 生成 outlet 组件,注意这里是从后往前 reduce,所以索引在前的 match 是最外层,也就是父路由对应的 match 是最外层 /** * 可以看到 outlet 是通过不断递归生成的组件,最外层的 outlet 递归层数最多,包含有所有的内层组件, * 所以我们在外层使用的 是包含有所有子组件的聚合组件 * */ return matches.reduceRight((outlet, match, index) => { return ( ,继续渲染内嵌的 children={ match.route.element !== undefined ? match.route.element : } // 代表当前 RouteContext 匹配到的值,matches 并不是全局状态一致的,会根据层级不同展示不同的值,最后一个层级是完全的 matches,这也是之前提到过的不要在外部使用 RouteContext 的原因 value={{ outlet, matches: parentMatches.concat(matches.slice(0, index + 1)) }} /> ); // 最内层的 outlet 为 null,也就是最后的子路由 }, null as React.ReactElement | null); } ``` ## 4. React Router API API参考:https://reactrouter.com/docs/en/v6/routers/browser-router ### 4.1 routers - **browserRouter:浏览器router** - hashRouter - HistoryRouter:使用history库作为入参,这允许您在非 React context中使用history实例作为全局变量,标记为unstable_HistoryRouter,后续可能会被修改,不建议直接引用,可以从react-router中引入不建议使用 - MemoryRouter:不依赖于外界(如 browserRouter的 history 堆栈),常用于测试用例 - NativeRouter:RN环境下使用的router - Router:可以视为所有其他router的基类 - StaticRouter:node环境下使用的router ### 4.2 components - Link:在react-router-dom中,Link被渲染为有真实href的,同时,Link to 支持相对路径路由 - NavLink:有“active”标的Link,尝被用于导航栏等场景 - Navigate:可以理解为被useNavigate包裹的组件,作用通Link类似 - Outlet:类似slot,向下传递route - Routes & Route:URL变化时,Routes匹配出最符合要求的Routes渲染 ### 4.3 Hooks - useHref:被用作返回Link to 指定的URL - useInRouterContext :返回是否在的context中 - useLinkClickHandler:在使用自定义后返回点击事件 - useLinkPressHandler:类似useLinkClickHandler,用于RN - useLocation:返回当前的location对象 - useMatch:返回当前path匹配到的route - useNavigate:类似于Navigate,显示声明使用 - useNavigationType:pop、push、replace - useOutlet;获取此route层级的子router元素 - useOutletContext:用于向子route传递context - useParams:匹配当前路由path - useResolvedPath:返回当前路径的完整路径名,主要用于相对子route中 - useRoutes:等同于,但要接收object形式 - useSearchParams:用于查询和修改location 中query字段 - useSearchParams(RN):RN中使用