React-router

叶德运
2023-12-01

React Router使用用法

React Router功能介绍

是React生态库之一,是可以在CSR和SSR环境下为了React而设计的路由库。

  1. 客户端:React环境
  2. 服务端: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相对更“智能”。

  1. teams/111:匹配<Team />
  2. 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的一些问题:

  1. 为什么没有withRouter了?
    1. withRouter用处:将一个组件包裹进Route里,然后react-router的三个对象history,location,match就会被放进这个组件的props属性中,可以实现对应的功能。
    2. 引入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;
}
  1. V6以下的版本中,支持<Route component>和<Route render>,为什么V6只支持<Route element>?
    1. 参考React的Suspense的用法,<Suspense fallback={<Spinner/>}>,传入的是React元素,而非组件,可以将props更容易地传入到对应的元素内。(推荐)
    2. 可以隐式地传递props到元素内。
    3. 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();
}
  1. 如何在树形结构中嵌套路由?
//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>    
    )
}
  1. 为什么取消正则路由?
    1. 正则路由为V6版本的路由排序带来很多问题,比如定义一个正则的优先级。
    2. 正则路由占据了React Router近1/3的体积。
    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

  1. routers:
    1. browserRouter:浏览器router,web开发首选;
    2. hashRouter:在不能使用browserRouter时使用,常见SPA的B端项目;
    3. historyRouter:使用history库作为入参,允许在非React context中使用history实例作为全局变量,标记为unstable_HistoryRouter,后续可能会被修改,不建议直接使用。
    4. MemoryRouter:不依赖于外界(如browserRouter的history堆栈),常用于测试用例。
    5. NativeRouter:RN环境下使用的router。
    6. StaticRouter:node环境下使用的router。
    7. Router:所有router的基类。
  2. components:
    1. Link:在react-router-dom中,Link被渲染为有真实href的<a/>,同时,Link to支持相对路径路由。
    2. Link(RN)
    3. NavLink:有“active”标的Link,常被用于导航栏等场景。
    4. Navigate:可以理解为被useNavigate包裹的组件,作用同Link类似。
    5. Outlet:类似slot,向下传递route。
    6. Routes&Route:URL变化时,Routes匹配出最符合要求的Routes渲染。
  3. Hooks:
    1. useHref:被用作返回Link to指定的URL。
    2. useInRouterContext:返回是否在<Router>的context中。
    3. useLinkClickHandler:在使用自定义<Link>后返回点击事件。
    4. useLinkPressHandler:类useLinkClickHandler,用于RN。
    5. seLocation:返回当前的location对象。
    6. useMatch:返回当前path匹配到的route。
    7. useNavigate:类似Navigate,显示声明使用。
    8. useNavigationType:pop、push、replace。
    9. useOutlet:获取此route层级的子router元素。
    10. useOutletContext:用于向子route传递context。
    11. useParams:匹配当前路由path。
    12. useResolvedPath:返回当前路径的完整路径名,主要用于相对子route中。
    13. useRoutes:等同于<Routes>,但要接收object形式。
    14. useSearchParams:用于查询和修改location中query字段。
    15. useSearchParams(RN):RN中使用。
  4. utilities:
    1. createRoutesFromChildren:将<Route>转为route object形式。
    2. createSearchParams:类似useSearchParams。
    3. generatePath:将通配符和动态路由和参数转为真实path。
    4. Location:用于history router,声明Location的interface
    5. matchPath:类似useMatch,返回匹配到的route path。
    6. matchRoutes:返回匹配到的route 对象。
    7. renderMatches:返回matchRoutes的react元素。
    8. resolvePath:将Link to的值转为带有绝对路径的真实的path对象。

手写一个简单的react-router

前置基础

SPA(单页应用)
特点:只会在首次加载时,向服务器请求资源以加载页面后续跳转页面是不会再向服务器请求资源,并且不会重新加载页面,会以切换组件重新渲染来达到页面跳转的目的。

页面刷新的场景:

  1. 在JS中发起页面跳转,改变浏览器的URL
  2. 用户提供点击浏览器的前进或后退按钮发生页面跳转
  3. 用户修改浏览器URL导致重新加载页面

History API(对访问页面堆栈的操作):可以修改浏览器的URL,但是不会重新加载页面。

  1. pushState:创建一个新的URL并跳转。
  2. replaceState:修改当前URL。
  3. back:返回后一个URL。
  4. forward:返回前一个URL。
  5. go:跳转到指定页面的URL,没有传参时则会与调用location.reload()一样重新加载页面。

监听用户点击浏览器前进和后退按钮:

  1. 提供监听popstate实现。
  2. 调用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核心功能:

  1. 订阅和操作history堆栈
  2. 将URL与router匹配
  3. 渲染与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。

  1. push:将新的入口添加到history stack(点击链接或者navigation)
  2. replace:代替当前的堆栈信息,而不是新push
  3. 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 都是在这里定义的。

  1. index.ts:入口文件,且标识了三个不安全的API,要使用的话,不要单独从lib/context.ts引入,要从react-router的入口文件引入(虽然一般开发中用不到)
  2. router:Router在react-router内部主要用于提供全局的路由导航对象(一般由history库提供)以及当前的路由导航状态,在项目中使用时一般是必须并且唯一的,不过一般不会直接使用,更多会使用已经封装好路由导航对象的BrowserRouter(react-router-dom包引入)、HashRouter(react-router-dom包引入)和MemoryRouter(react-router包引入)
  3. Hooks:基于LocationContext的三个 hooks:useInRouterContext、useNavigationType、useLocation
  4. 定义Router组件:传入Context与外部传入的location
  5. memory router封装:其实就是将history库与我们声明的Router组件绑定起来,当history.listen监听到路由改变后重新设置当前的location与action。
  6. 总结:
    1. Router组件是react-router应用中必不可少的,一般直接写在应用最外层,它提供了一系列关于路由跳转和状态的上下文属性和方法
    2. 一般不会直接使用Router组件,而是使用react-router内部提供的高阶Router组件,而这些高阶组件实际上就是将history库中提供的导航对象与Router组件连接起来,进而控制应用的导航状态

route:

  1. props:route在react-router中只是提供命令式的路由配置方式。
    1. 三种类型:PathRouteProps、LayoutRouteProps、IndexRouteProps。
  2. Route 组件内部没有进行任何操作,仅仅只是定义 props,而我们就是为了使用它的 props。
  3. 总结:Route可以被看作是一个挂载用户传入参数的对象,它不会在页面中渲染,而是会被Routes接收并解析

routes:

  1. createRoutesFromChildren
  2. useRoutes:声明式配置路由
  3. 总结:
    1. react-router在路由定义时包含两种方式:指令式<routes><route /></routes>,指令式内部也是声明式形式;声明式useRoutes
    2. outes与Route强绑定,有Routes则必定要传入且只能传入Route

useRoutes:

  1. RouteContext:动态参数
  2. 拆解useRoutes:
    1. 该hooks不是只调用一次,每次重新匹配到路由时就会重新调用渲染新的element
    2. 当多次调用useRoutes时需要解决内置的route上下文问题,继承外层的匹配结果
    3. 内部通过计算所有的routes与当前的location关系,经过路由
  3. 总结:
    1. 获取上下文中调用useRoutes后的信息,如果证明此次调用时作为子路由使用的,需要合并父路由的匹配信息——路由上下文解析阶段
    2. 移除父路由已经匹配完毕的pathname前缀后,调用matchRoutes与当前传入的routes配置相匹配,返回匹配到的matches数组——路由匹配阶段
    3. 调用_renderMatches方法,渲染上一步得到的matches数组——路由渲染阶段
  4. matchRoutes:包含的方面如下
    1. flattenRoutes:扁平化,将树形结构转为一维数组
    2. rankRouteBranches:排序
    3. matchRouteBranch:路由匹配,_renderMatches
  5. 总结:
    1. useRoutes是react-router中核心,用户不管是直接使用useRoutes还是用Routes与Route组件结合最终都会转换为它。该hook拥有三个阶段:路由上下文解析阶段、路由匹配阶段、路由渲染阶段;
    2. useRoutes在上下文解析阶段会解析在外层是否已经调用过useRoutes,如果调用过会先获取外层的上下文数据,最后将外层数据与用户传入的routes数组结合,生成最终结果;
    3. useRoutes在匹配阶段会将传入的routes与当前的location(可手动传入,但内部会做校验)做一层匹配,通过对route中声明的path的权重计算,拿到当前pathname所能匹配到的最佳matches数组,索引从小到大层数关系从外到内;
    4. useRoutes在渲染阶段会将matches数组渲染为一个聚合的React Element,该元素整体是许多 RouteContext.Provider的嵌套,从外到内依次是【父 => 子 => 孙子】这样的关系,每个 Provider包含两个值,与该级别对应的matches数组(最后的元素时该级别的route自身)与outlet元素,outlet元素就是嵌套RouteContext.Provider存放的地方,每个RouteContext.Provider的children就是route的element属性;
    5. 每次使用outlet实际上都是渲染的内置的路由关系(如果当前route没有element属性,则默认渲染outlet,这也是为什么可以直接写不带element的<Route/>组件嵌套的原因),我们可以在当前级别route的element中任意地方使用outlet来渲染子路由。

Navigate:
内部还是调用的useNavigate,而useNavigate内部则是对用户传入的路径做处理,获取到最终的路径值,再传递给NavigationContext提供navigator对象。

react-router-dom

BrowserRouter 和 HashRouter的区别,是区分链接还是hash,从history库中取到。

 类似资料: