React Router使用用法
React Router功能介绍
是React生态库之一,是可以在CSR和SSR环境下为了React而设计的路由库。
- 客户端:React环境
- 服务端:node、RN
安装:
npm install react-router-dom@6
yarn add react-router-dom@6
基本用法:
//src/index.js
import * as React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root');
);
root.render(
<React.StrictMode>
<BrowserRouter>
<App/>
</BrowserRouter>
</React.StrictMode>
);
//src/App.js
import * as React from 'react';
import {Routes, Route, Link} from 'react-router-dom';
import './App.css';
function App() {
return (
<div className='App'>
<h1>Welcome to React Router!</h1>
<Routes>
<Route path='/' element={<Home/>} />
<Route path='about' element={<About/>} />
</Routes>
</div>
)
}
//Home.jsx
function Home() {
return (
<>
<main>
<h2>Welcome to the homepage!</h2>
<p>you can do this, I believe in you.</p>
</main>
<nav>
<Link to='/about'>About</Link>
</nav>
</>
)
}
//About.jsx
function About() {
return (
<main>
<h2>who are we?</h2>
<p>
that feels like an existential question, don't you think?
</p>
</main>
<nav>
<Link to='/'>Home</Link>
</nav>
)
}
配置路由:
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const root = ReactDOM.createRoot(
document.getElementById('root');
);
root.render(
<BrowserRouter>
<Routes>
<Route path='/' element={<App/>}>
<Route index element={<Home/>} />
<Route path='teams' element={<Team/>}>
<Route path=':teamId' element={<Team/>} />
<Route path='new' element={<NewTeamForm/>} />
<Route index element={<LeagueStandings/>} />
</Route>
</Route>
</Routes>
</BrowserRouter>
)
在先前版本的React Router中,针对多个匹配到的router,需要声明出具体的匹配逻辑,但V6相对更“智能”。
- teams/111:匹配<Team />
- teams.new:匹配下面的<NewTeamForm />
修改url的方式:
//Link
import {Link} from 'react-router-dom';
function Home() {
return (
<div>
<h1>Home</h1>
<nav>
<Link to='/'>Home</Link> | {" "}
<Link to='about'>About</Link>
</nav>
</div>
)
}
//useNavigate:更多用于JS操作后跳转使用
import { useNavigate } from 'react-router-dom';
function Invoices() {
let navigate = useNavigate();
return (
<div>
<NewInvoiceForm onSubmit={async (event) => {
let newInvoice = await createInvoice(event.target);
navigate(`/invoices/${newInvoice.id}`);
}} />
</div>
)
}
使用url的路径参数,常用于匹配path参数后fetch数据:
import { Routes, Route, useParams } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path='invoices/:invoiceId' element={<Invoice/>} />
</Routes>
);
}
function Invoice() {
let params = useParams();
return <h1>Invoice{params.invoiceId}</h1>;
}
//example
function Invoice() {
let {invoiceId} = useParams();
let invoice = useFakeFetch(`/api/invoices/${invoiceId}`);
return invoice ? (
<div>
<h1>{invoice.customerName}</h1>
</div>
) : (
<Loading />
)
}
嵌套路由:
function App() {
return (
<Routes>
<Route path='invoices' element={<Invoices />}>
<Route path=':invoiceId' element={<Invoice/>} />
<Route path='sent' element={<SentInvoices/>}/>
</Route>
</Routes>
)
}
//提供三种路由:
///invoices
///invoices/sent
<App>
<Invoices>
<SentInvoices/>
</Invoices>
</App>
///invoices/:invoiceId
<App>
<Invoices>
<Invoice/>
</Invoices>
</App>
//父router中子router可以用<Outlet>表示
import {Routes, Route, Outlet} from 'react-router-dom';
function App() {
return (
<Routes>
<Route path='invoices' element={<Invoices/>}>
<Route path=":invoiceId" element={<Invoices/>} />
<Route path="sent" element={<SentInvoices/>} />
</Route>
</Routes>
)
}
function Invoices() {
return (
<div>
<h1>Invoices</h1>
<Outlet /> //匹配对应的<Invoice/>或<SentInvoices/>
</div>
)
}
function Invoice() {
let { invoiceId } = useParams();
return <h1>Invoice {invoiceId}</h1>;
}
//在根router中添加Link跳转
import { Routes, Route, Link, Outlet } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path='/' element={<Layout/>}>
<Route path='invoices' element={<Invoices/>} />
<Route path='dashboard' element={<Dashboard/>} />
</Routes>
</Routes>
)
}
function Layout() {
return (
<h1>Welcome to the app!</h1>
<nav>
<Link to='invoices'>Invoices</Link> | {" "}
<Link to='dashboard'>Dashboard</Link>
</nav>
<div className='content'>
<Outlet />
</div>
)
}
function Invoices() {
return <h1>Invoices</h1>;
}
function Dashboard() {
return <h1>Dashboard</h1>;
}
index routes:
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Activity />} />
<Route path="invoices" element={<Invoices />} />
<Route path="activity" element={<Activity />} />
</Route>
</Routes>
);
}
function Layout() {
return (
<div>
<GlobalNav />
<main>
<Outlet />
</main>
</div>
);
}
// 如果是 "/"
<App>
<Layout>
<Activity />
</Layout>
</App>
relative links:link to 指向的是相同级别的路由。
import { Routes, Route, Link, Outlet } from 'react-router-dom';
function Home() {
return <h1>Home</h1>;
}
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<nav>
<Link to='invoice'>Invoice</Link> //dashboard/invoices
<Link to='team'>Team</Link> //dashboard/team
</nav>
<hr/>
<Outlet/>
</div>
)
}
function Invoices() {
return <h1>Invoices</h1>;
}
function Team() {
return <h1>Team</h1>;
}
function App() {
return (
<Routes>
<Route path='/' element={<Home/>} />
<Route path='dashboard' element={<Dashboard/>}>
<Route path='invoices' element={<Invoices/>} />
<Route path='team' element={<Team/>} />
</Route>
</Routes>
)
}
兜底routes,多个routes集成在一个组件。
function App() {
return (
<div>
<Sidebar>
<Routes>
<Route path='/' element={<MainNav/>} />
<Route path='dashboard' element={<DashboardNav/>} />
</Routes>
</Sidebar>
<MainContent>
<Routes>
<Route path='/' element={<Home/>}>
<Route path='about' element={<About/>} />
<Route path='support' element={<Support/>} />
</Route>
<Route path='dashboard' element={<Dashboard/>}>
<Route path='invoices' element={<Invoices/>} />
<Route path='team' element={<Team/>} />
</Route>
<Route path='*' element={<NotFound/>} />
</Routes>
</MainContent>
</div>
)
}
升级到V6的一些问题:
- 为什么没有withRouter了?
- withRouter用处:将一个组件包裹进Route里,然后react-router的三个对象history,location,match就会被放进这个组件的props属性中,可以实现对应的功能。
- 引入V6中,更多地使用hooks语法,而withRouter的用法更多用在Class组件中,只要可以将类组件转为函数组件即可。
import { useLocation, useNavigate, useParams } from 'react-router-dom';
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component {...props} router={{ location, navigate, params }} />
);
}
return ComponentWithRouterProp;
}
- V6以下的版本中,支持<Route component>和<Route render>,为什么V6只支持<Route element>?
- 参考React的Suspense的用法,<Suspense fallback={<Spinner/>}>,传入的是React元素,而非组件,可以将props更容易地传入到对应的元素内。(推荐)
- 可以隐式地传递props到元素内。
- V6以下形式的包版本体积过大。
//V6以下
<Route path=":userId" component={Profile} />
<Route path=":userId" render={routeProps => (
<Profile routeProps={routeProps} animate={true} />
)}>
<Route path=":userId" children={({ match }) => ( match ?
(
<Profile match={match} animate={true} />
)
) : (
<NotFound />
)}>
//V6
<Route path=":userId" element={<Profile/>} />
<Route path=":userId" element={<Profile animate={true}/>} />
function Profile({ animate }) {
//使用hooks,在元素定义内处理逻辑
let params = useParams();
let location = useLocation();
}
- 如何在树形结构中嵌套路由?
//V6以下
<Switch>
<Route path="/users" component={Users} />
</Switch>;
function Users() {
return (
<div>
<h1>User</h1>
<Switch>
<Route path="/users/account" component={Account} />
</Switch>
</div>
)
}
//V6
<Routes>
<Route path='/users/*' element={<User/>} />
</Routes>;
function Users() {
return (
<div>
<h1>User</h1>
<Routes>
<Route path="account" element={<Account/>} />
</Routes>
</div>
)
}
- 为什么取消正则路由?
- 正则路由为V6版本的路由排序带来很多问题,比如定义一个正则的优先级。
- 正则路由占据了React Router近1/3的体积。
- 正则路由能表达的,V6版本都支持。
//V5
function App() {
return (
<Switch>
<Route path={/(en|es|fr)/} component={Lang} />
</Switch>
);
}
function Lang({ params }) {
let lang = params[0];
let translations = I81n[lang];
//...
}
//V6
function App() {
return (
<Routes>
<Route path="en" element={<Lang lang="en"/>} />
<Route path="es" element={<Lang lang="es"/>} />
<Route path="fr" element={<Lang lang="fr"/>} />
</Routes>
);
}
function Lang({ lang }) {
let translations = I81n[lang];
//...
}
//V5
function App() {
return (
<Switch>
<Route path={/users\/(\d+)/} component={User} />
</Switch>
)
}
function User({ params }) {
let id = params[0];
//...
}
//V6
function App() {
return (
<Routes>
<Route path="/users/:id" element={<ValidateUser/>} />
<Route path="/users/*" element={<NotFound/>} />
</Routes>
);
}
function ValidateUser() {
let params = useParams();
let userId = params.id.match(/\d+/);
if(!userId) {
return <NotFound />
}
return <User id={params.userId} />;
}
function User(props) {
let id = props.id;
//...
}
React Router API
- routers:
- browserRouter:浏览器router,web开发首选;
- hashRouter:在不能使用browserRouter时使用,常见SPA的B端项目;
- historyRouter:使用history库作为入参,允许在非React context中使用history实例作为全局变量,标记为unstable_HistoryRouter,后续可能会被修改,不建议直接使用。
- MemoryRouter:不依赖于外界(如browserRouter的history堆栈),常用于测试用例。
- NativeRouter:RN环境下使用的router。
- StaticRouter:node环境下使用的router。
- Router:所有router的基类。
- components:
- Link:在react-router-dom中,Link被渲染为有真实href的<a/>,同时,Link to支持相对路径路由。
- Link(RN)
- NavLink:有“active”标的Link,常被用于导航栏等场景。
- Navigate:可以理解为被useNavigate包裹的组件,作用同Link类似。
- Outlet:类似slot,向下传递route。
- Routes&Route:URL变化时,Routes匹配出最符合要求的Routes渲染。
- Hooks:
- useHref:被用作返回Link to指定的URL。
- useInRouterContext:返回是否在<Router>的context中。
- useLinkClickHandler:在使用自定义<Link>后返回点击事件。
- useLinkPressHandler:类useLinkClickHandler,用于RN。
- seLocation:返回当前的location对象。
- useMatch:返回当前path匹配到的route。
- useNavigate:类似Navigate,显示声明使用。
- useNavigationType:pop、push、replace。
- useOutlet:获取此route层级的子router元素。
- useOutletContext:用于向子route传递context。
- useParams:匹配当前路由path。
- useResolvedPath:返回当前路径的完整路径名,主要用于相对子route中。
- useRoutes:等同于<Routes>,但要接收object形式。
- useSearchParams:用于查询和修改location中query字段。
- useSearchParams(RN):RN中使用。
- utilities:
- createRoutesFromChildren:将<Route>转为route object形式。
- createSearchParams:类似useSearchParams。
- generatePath:将通配符和动态路由和参数转为真实path。
- Location:用于history router,声明Location的interface
- matchPath:类似useMatch,返回匹配到的route path。
- matchRoutes:返回匹配到的route 对象。
- renderMatches:返回matchRoutes的react元素。
- resolvePath:将Link to的值转为带有绝对路径的真实的path对象。
手写一个简单的react-router
前置基础
SPA(单页应用)
特点:只会在首次加载时,向服务器请求资源以加载页面,后续跳转页面是不会再向服务器请求资源,并且不会重新加载页面,会以切换组件重新渲染来达到页面跳转的目的。
页面刷新的场景:
- 在JS中发起页面跳转,改变浏览器的URL
- 用户提供点击浏览器的前进或后退按钮发生页面跳转
- 用户修改浏览器URL导致重新加载页面
History API(对访问页面堆栈的操作):可以修改浏览器的URL,但是不会重新加载页面。
- pushState:创建一个新的URL并跳转。
- replaceState:修改当前URL。
- back:返回后一个URL。
- forward:返回前一个URL。
- go:跳转到指定页面的URL,没有传参时则会与调用location.reload()一样重新加载页面。
监听用户点击浏览器前进和后退按钮:
- 提供监听popstate实现。
- 调用history.pushState()或history.replaceState()不会触发popstate事件,popstate只会在浏览器某些行为下触发,如点击后退、前进按钮,或者在JavaScript中调用history.back()、history.forward()、history.go()方法,此外,a标签的锚点也会触发该事件。
实现一个BrowserRouter
function BrowserRouter(props) {
const RouterContext = createContext();
const HistoryContext = createContext();
const [path, setPath] = useState(() => {
//首次渲染,获取到对应的路由
const { pathname } = window.location;
return pathname || '/';
});
useEffect(() => {
//监听用户点击浏览器的前进、后退按钮跳转到页面
window.addEventListener('popstate', handlePopstate);
return () => {
window.removeEventListener('popstate', handlePopstate);
}
}, []);
const handlePopstate = function(event) {
const { pathname } = window.location;
setPath(pathname);
}
//点击UI跳转页面
const push = function(path) {
setPath(path);
window.history.pushState({path}, null, path);
}
const goBack = function() {
window.history.go(-1);
}
return (
<RouterContext.Provider value={path}>
<HistoryContext.Provider value={{ push, goBack }}>
{props.children}
</HistoryContext.Provider>
</RouterContext.Provider>
)
}
export default BrowserRouter;
//Route
export function Route(props) {
const {component: Component, path: componentPath} = props;
return (
<RouterContext.Consumer>
{(path) => {
return componentPath === path ? <Component/> : null;
}}
</RouterContext.Consumer>
)
}
//为什么不使用useContext:
//因为每当路由变化时,我们都需要重新渲染一个对应的组件,需要监听path的变化
实现一个HashRouter
import { useEffect, useState } from "react";
import RouterContext from './routerContext';
import HistoryContext from './historyContext';
//自定义HashRouter
function HashRouter(props) {
const [path, setPath] = useState(() => {
const {hash} = window.location;
if (hash) {
return hash.slice(1);
}
return '/#/';
});
useEffect(() => {
//监听用户点击浏览器的前进,后退按钮跳转到页面
window.addEventListener('hashchange', handlePopstate);
return () => {
window.removeEventListener('hashchange', handlePopstate);
}
}, []);
const handlePopstate = function(event) {
const {hash} = window.location;
setPath(hash.slice(1));
}
//点击UI跳转页面
const push = function(path) {
window.location.hash = path;
}
const goBack = function() {
window.history.go(-1);
}
return (
<RouterContext.Provider value={path}>
<HistoryContext.Provider value={{ push, goBack }}>
{props.children}
</HistoryContext.Provider>
</RouterContext.Provider>
)
}
export default HashRouter;
//Route
export function Route(props) {
const {component: Component, path: componentPath} = props;
return (
<RouterContext.Consumer>
{(path) => {
return componentPath === path ? <Component/> : null;
}}
</RouterContext.Consumer>
)
}
React Router原理解析
React Router核心功能:
- 订阅和操作history堆栈
- 将URL与router匹配
- 渲染与router相匹配的UI
概念定义
URL:地址栏中的URL。
Location:由React Router基于浏览器内置的window.location对象封装而成的特定对象,代表用户在哪里,基本代表了URL。
Location State:不在URL中,但代表了Location的状态。
History Stack:随着用户操作导航,浏览器会保留location的堆栈,可以通过返回前进按钮操作。
Client Side Routing (CSR) :一个纯 HTML 文档可以通过history stack来链接到其他文档,CSR使我的能够操作浏览器历史堆栈,而无需向服务器发出文档请求。
History:一个object,它允许 React Router 订阅 URL 中的更改,并提供 API 以编程方式操作浏览器历史堆栈。
History Action :包括POP、PUSH、或者 REPLACE。
- push:将新的入口添加到history stack(点击链接或者navigation)
- replace:代替当前的堆栈信息,而不是新push
- pop:当用户点击后推或者前进按钮
Segment :/ 字符之间的 URL 或 path pattern部分。例如,“/users/123”有两个segment。
Path Pattern:看起来像 URL,但可以具有用于将 URL 与路由匹配的特殊字符,例如动态段 (“/users/:userId”) 或通配符 (“/docs/*”)。它们不是 URL,它们是 React Router 将匹配的模式。
Dynamic Segment:动态的path pattern,例如,/users/:userId 将会匹配 /user/123。
URL Params : 动态段匹配的 URL 的解析值。
Router :使所有其他组件和hooks工作的有状态的最高层的组件。
Route Config:将当前路径进行匹配,通过排序和匹配创建一个树状的routes对象。
Route:通常具有 { path, element } 或 <Route path element> 的路由元素。path是 pattern。当路径模式与当前 URL 匹配时展示。
Route Element: 也就是 <Route>,<Routes> 读取该元素的 props 以创建路由。
Nested Routes:因为路由可以有子路由,并且每个路由通过segment定义 URL 的一部分,所以单个 URL 可以匹配树的嵌套“分支”中的多个路由。这可以通过outlet、relative links等实现自动布局嵌套。
Relative links:不以 / 开头的链接,继承渲染它们的最近路径。在无需知道和构建整个路径的情况下,就可以实现更深层的url macth。
Match:当路由匹配 URL 时保存信息的对象,例如匹配的 url params和path name。
Matches:与当前位置匹配的路由数组,此结构用于nested routes。
Parent Route:带有子路由的父路由节点。
Outlet: 匹配match中的下一个匹配项的组件。
Index Route :当没有path时,在父路由的outlet中匹配。
Layout Route: 专门用于在特定布局内对子路由进行分组。
history和location
React Router 的前提是:它必须能够订阅浏览器history stack中的更改。
浏览器在用户浏览时维护自己的历史堆栈。这就是后退和前进按钮的工作方式。在传统网站(没有 JavaScript 的 HTML 文档)中,每次用户单击链接、提交表单或单击后退和前进按钮时,浏览器都会向服务器发出请求。
history object:通过客户端路由(CSR),我们可以通过代码操纵浏览器历史记录栈。
//可以写一些这样的代码来改变URL,而不需要浏览器向服务器发出请求的默认行为
<a href='/contact' onClick={(event) => {
//阻止默认事件
event.preventDefault();
//push将URL转向/contact
window.history.pushState({}, undefined, '/contact');
}} />
//以上代码会修改URL,但不会渲染任何UI的变化,需要监听变化,并通过代码修改页面UI
window.addEventListener('popstate', () => {
//url changed
});
//但此类事件只在点击前进后退按钮才生效,对window.history.pushState或者window.history.replaceState无效
因此,React Router使用history对象来监听事件的变化,如POP、PUSH、REPLACE。
let history = createBrowserHistory();
history.listener(({location, action}) => {
//this is called whenever new locations come in the action is POP, PUSH or REPLACE
})
//在开发环境中,我们不需要关系history object,这些在React Router底层实现了,React Router提供监听history stack的变化,最终在URL变化时更新其状态,并重新渲染
location:
React Router 声明了自己的location模块,大致为:
{
pathname: "/bbq/pig-pickins",
search: "?campaign=instagram",
hash: "#menu",
state: null,
key: "aefz24ie"
}
//pathname、search、hash大致同window.location一致,三者拼接起来等同于URL
location.pathname + location.search + location.hash;
// /bbq/pig-pickins?campaign=instagram#menu
可使用urlSearchParams来获取对应的search内容:
// given a location like this:
let location = {
pathname: "/bbq/pig-pickins",
search: "?campaign=instagram&popular=true",
hash: "",
state: null,
key: "aefz24ie",
};
// we can turn the location.search into URLSearchParams
let params = new URLSearchParams(location.search);
params.get("campaign"); // "instagram"
params.get("popular"); // "true"
params.toString(); // "campaign=instagram&popular=true"
location state:
// 通过pushState注入堆栈,goback()时退出一层堆栈
window.history.pushState("look ma!", undefined, "/contact");
window.history.state; // "look ma!"
// user clicks back
window.history.state; // undefined
// user clicks forward
window.history.state; // "look ma!"
可以将location.state 当做跟URL变动而变动的属性,只是一般用于开发者使用。
在React Router中,我们可以通过Link 或者Navigate 来设置state,并使用useLocation获取state。
<Link to="/pins/123" state={{ fromDashboard: true }} />;
let navigate = useNavigate();
navigate("/users/123", { state: partialUser });
let location = useLocation();
location.state;
location key:一般用于定位滚动距离,或者客户端数据缓存等,因为每个堆栈都有唯一的key值,可以通过Map或者localStorage来标识指定的堆栈信息。
//根据location.key缓存数据
let cache = new Map();
function useFakeFetch(URL) {
let location = useLocation();
let cacheKey = location.key + URL;
let cached = cache.get(cacheKey);
let [data, setData] = useState(() => {
//initialize from the cache
return cached || null;
});
let [state, setState] = useState(() => {
//avoid the fetch if cached
return cached ? 'done' : 'loading';
});
useEffect(() => {
if (state === 'loading') {
let controller = new AbortController();
fetch(URL, { signal: controller.signal }).then((res) => res.json()).then((data) => {
if (controller.signal.aborted) {
return;
}
cache.set(cacheKey, data);
setData(data);
});
return () => controller.abort();
}
}, [state, cacheKey]);
useEffect(() => {
setState('loading');
}, [URL]);
return data;
}
匹配
在初始渲染时,当历史堆栈发⽣变化时,React Router 会将位置与您的路由配置进行匹配,以提供⼀组要渲染的匹配项。对应的routes,可以使用useRoutes(routesGoHere)获取。
因为routes是树状结构,因此,一个单一的URL可以匹配所有的树中的“分支”。
渲染
<Routes>将把位置与你的路由配置相匹配,得到一组匹配的内容,然后呈现一个React元素树。
outlets:
很像slot,<Outlet> 应该在父路由元素中使用以呈现子路由元素,以此让嵌套的子路由展示,当匹配到子路由的路径后,会展示,或不展示。
index routes:
index routes 会将父route的outlet渲染出来,一般会在持久化导航的父路由节点上展示默认的子路由信息。
layout routes:
实际上,layout routes (布局路由)本身不参与匹配,但其子route参与。
导航函数
可以使用useNavigate方法。但要注意不要随意使用navigate,会增加程序的复杂性。
let navigate = useNavigate();
useEffect(() => {
setTimeout(() => {
navigate('/layout');
}, 3000);
}, []);
//no
<li onClick={() => navigate('/somewhere')} />
//yes
<Link to="somewhere" />
数据获取
let location = useLocation();
let urlParams = useParams();
let [urlSearchParams] = useSearchParams();
react-router核心库
react-router:与运行环境无关,几乎所有与运行平台无关的方法、组件和 hooks 都是在这里定义的。
- index.ts:入口文件,且标识了三个不安全的API,要使用的话,不要单独从lib/context.ts引入,要从react-router的入口文件引入(虽然一般开发中用不到)
- router:Router在react-router内部主要用于提供全局的路由导航对象(一般由history库提供)以及当前的路由导航状态,在项目中使用时一般是必须并且唯一的,不过一般不会直接使用,更多会使用已经封装好路由导航对象的BrowserRouter(react-router-dom包引入)、HashRouter(react-router-dom包引入)和MemoryRouter(react-router包引入)
- Hooks:基于LocationContext的三个 hooks:useInRouterContext、useNavigationType、useLocation
- 定义Router组件:传入Context与外部传入的location
- memory router封装:其实就是将history库与我们声明的Router组件绑定起来,当history.listen监听到路由改变后重新设置当前的location与action。
- 总结:
- Router组件是react-router应用中必不可少的,一般直接写在应用最外层,它提供了一系列关于路由跳转和状态的上下文属性和方法。
- 一般不会直接使用Router组件,而是使用react-router内部提供的高阶Router组件,而这些高阶组件实际上就是将history库中提供的导航对象与Router组件连接起来,进而控制应用的导航状态。
route:
- props:route在react-router中只是提供命令式的路由配置方式。
- 三种类型:PathRouteProps、LayoutRouteProps、IndexRouteProps。
- Route 组件内部没有进行任何操作,仅仅只是定义 props,而我们就是为了使用它的 props。
- 总结:Route可以被看作是一个挂载用户传入参数的对象,它不会在页面中渲染,而是会被Routes接收并解析。
routes:
- createRoutesFromChildren
- useRoutes:声明式配置路由
- 总结:
- react-router在路由定义时包含两种方式:指令式<routes><route /></routes>,指令式内部也是声明式形式;声明式useRoutes
- outes与Route强绑定,有Routes则必定要传入且只能传入Route
useRoutes:
- RouteContext:动态参数
- 拆解useRoutes:
- 该hooks不是只调用一次,每次重新匹配到路由时就会重新调用渲染新的element
- 当多次调用useRoutes时需要解决内置的route上下文问题,继承外层的匹配结果
- 内部通过计算所有的routes与当前的location关系,经过路由
- 总结:
- 获取上下文中调用useRoutes后的信息,如果证明此次调用时作为子路由使用的,需要合并父路由的匹配信息——路由上下文解析阶段
- 移除父路由已经匹配完毕的pathname前缀后,调用matchRoutes与当前传入的routes配置相匹配,返回匹配到的matches数组——路由匹配阶段
- 调用_renderMatches方法,渲染上一步得到的matches数组——路由渲染阶段
- matchRoutes:包含的方面如下
- flattenRoutes:扁平化,将树形结构转为一维数组
- rankRouteBranches:排序
- matchRouteBranch:路由匹配,_renderMatches
- 总结:
- useRoutes是react-router中核心,用户不管是直接使用useRoutes还是用Routes与Route组件结合最终都会转换为它。该hook拥有三个阶段:路由上下文解析阶段、路由匹配阶段、路由渲染阶段;
- useRoutes在上下文解析阶段会解析在外层是否已经调用过useRoutes,如果调用过会先获取外层的上下文数据,最后将外层数据与用户传入的routes数组结合,生成最终结果;
- useRoutes在匹配阶段会将传入的routes与当前的location(可手动传入,但内部会做校验)做一层匹配,通过对route中声明的path的权重计算,拿到当前pathname所能匹配到的最佳matches数组,索引从小到大层数关系从外到内;
- useRoutes在渲染阶段会将matches数组渲染为一个聚合的React Element,该元素整体是许多 RouteContext.Provider的嵌套,从外到内依次是【父 => 子 => 孙子】这样的关系,每个 Provider包含两个值,与该级别对应的matches数组(最后的元素时该级别的route自身)与outlet元素,outlet元素就是嵌套RouteContext.Provider存放的地方,每个RouteContext.Provider的children就是route的element属性;
- 每次使用outlet实际上都是渲染的内置的路由关系(如果当前route没有element属性,则默认渲染outlet,这也是为什么可以直接写不带element的<Route/>组件嵌套的原因),我们可以在当前级别route的element中任意地方使用outlet来渲染子路由。
Navigate:
内部还是调用的useNavigate,而useNavigate内部则是对用户传入的路径做处理,获取到最终的路径值,再传递给NavigationContext提供navigator对象。
react-router-dom
BrowserRouter 和 HashRouter的区别,是区分链接还是hash,从history库中取到。