# 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中使用