前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React Router V6详解

React Router V6详解

作者头像
xiangzhihong
发布2023-01-06 09:13:43
7.7K0
发布2023-01-06 09:13:43
举报
文章被收录于专栏:向治洪向治洪

一、简介

1.1 SAP

SAP全称是【single-page application】,中文译为单页面应用。它是网站应用的一种模型,可以动态重写当前的页面来与用户交互,而不需要重新加载整个页面。相对于传统的 Web 应用程序,单页应用做到了前后端分离,即后端只负责处理数据提供接口,而页面逻辑和页面渲染都交由前端处理。前端发展到现在,单页应用的使用已经很广泛,目前时兴的 React、Vue、Angular 等前端框架均采用了 SPA 原则。

相比于传统的Web应用,SPA一个最重要的特性就是改变路由时不会触发整个页面的刷新,只会刷新需要刷新的模块或组件。要实现这种效果,通常有两种方式,分别似乎window.history和 location.hash。其中,window.history包含了浏览器的历史信息,主要的方法有history.back()、history.forward()和history.go(n)等。hash是location 对象的属性,它指的是当前链接的锚,也就是从【#】号开始的部分。

不过,虽然SPA有它的优点,也得到了主流框架的支持,但它也存在一定的局限性。比如,对 SEO不太优好;易出错,需要使用程序管理前进、后退、地址栏等操作。基于此,在一些中大型项目中,我们更推荐使用路由的概念来管理应用的页面。

1.2 路由

在前端应用中,路由可以理解为是一种映射关系,即路径与组件/函数的对应关系,比如,当用户访问’/dashboard’时,页面将呈现各种仪表板组件,如图表和表格;当用户访问’/user’时,页面将列出各种用户属性。

在基于React的前端架构中,React是不附带路由库的,所以要管理多个路由页面就需要使用到第三方库,比如React Router。事实上,react-router并不是一个库,塔包含3个库:react-router、react-router-dom和react-router-native,分别用来适配浏览器环境和手机原生环境。并且,react-router-dom和 react-router-native都需要依赖react-router,所以在安装时会自动安装react-router。

目前,React Router已经发布了V6版本,用法和组件相比之前的版本也有一些变化,总结如下:

  • 重命名为;
  • 的新特性变更,如component/render被element替代、routeProps可以在element中直接获取等;
  • 标签支持嵌套,可以在一个文件内配置嵌套路由;
  • 新钩子useRoutes代替react-router-config;
  • useNavigate代替useHistory;
  • Link不再支持component属性;
  • NavLink 的exact属性替换为end;
  • 添加Outlet组件,用于渲染子路由;

使用之前,可以先使用下面的命令进行安装。

代码语言:javascript
复制
npm:npm install react-router-dom@6
//或者
yarn:yarn add react-router-dom@6

1.3 路由模式

在单页面应用中,为了实现切换页面不刷新浏览器的功能在React Router提供了两种,有两种路由模式,分别是hash路由模式和history路由模式。

HashRouter

HashRouter基于Hash模式,页面跳转基于location.hash和location.replace实现;基于Hash模式的路由,在域名后通常以【#】号开头,再拼接路径,格式为:http://www.abc.com/#/xx

History

History基于history模式,页面跳转使用的是HTML5为浏览器全局的history对象来实现的,即 history.pushState和history.replaceState。History相比HashRouter更加优雅,比如:http://www.abc.com/xx

二、基本使用

2.1 基础API

2.1.1 配置路由

使用BrowserRouter路由模式时,需要先在应用的入口文件中进行路由的申明和配置,如下所示。

代码语言:javascript
复制
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 element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />   
          <Route path="new" element={<NewTeamForm />} />  
          <Route index element={<LeagueStandings />} />
        </Route>
      </Route>
    </Routes>
  </BrowserRouter>
);

完成路由的定义之后,接下来,只需要在使用的地方使用history.push()方法即可打开新页面。

代码语言:javascript
复制
history.push("teams")

2.1.2 Link

除了声明路由饿的方式外,我们还可以使用Link组件来打开一个新页面,Link组件最终会被渲染成a元素,最常见的场景就是打开一个网页页面。打开一个新页面时,需要添加to属性。

代码语言:javascript
复制
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>
  );
}

2.1.3 Navigation

为了React Hook,react-router-dom还提供了useNavigate,也能够实现路由操作。

代码语言:javascript
复制
import { useNavigate } from "react-router-dom";
function Invoices() {
  let navigate = useNavigate();
  return (
    <div>
      <NewInvoiceForm onSubmit={() => navigate(`/invoices/${newInvoice.id}`)} />
    </div>
  );
}

2.1.4 获取路由参数

在两个页面进行跳转的过程中,必然会涉及参数值传递的问题,那怎么拿到上一个页面的传递的参数值呢?此时需要用到useParams()。

代码语言:javascript
复制
import { Routes, Route, useParams } from "react-router-dom";
function App() {
  return (
    <Routes>
      <Route path="invoices/:invoiceId" element={<Invoice />}/>
    </Routes>
  );
}
function Invoice() {
  let params = useParams();        // 第一种
  let { invoiceId } = useParams(); // 第二种
  return <h1>Invoice {params.invoiceId}</h1>;
}

2.1.5 嵌套路由

如果项目中涉及到嵌套路由,路由路径匹配url路径定义如下。

代码语言:javascript
复制
function App() {
  return (
    <Routes>
      <Route path="invoices" element={<Invoices />}>         // /invoices
        <Route path=":invoiceId" element={<Invoice />} />    // /invoices/:invoiceId
        <Route path="sent" element={<SentInvoices />} />     // /invoices/sent
      </Route>
    </Routes>
  );
}

而父router中子router可以用组件表示,然后Link修改url。

代码语言:javascript
复制
function Invoices() {
  return (
    <div>
      <nav>
        <Link to="invoices">Invoices</Link>
        <Link to="dashboard">Dashboard</Link>
      </nav>
      <Outlet /> // 匹配对应的<Invoice /> 或者 <SentInvoices />
    </div>
  );
}

2.1.6 兜底路由

在React应用中,为了防止路由匹配失败的情况,我们还需要配置一个默认路由。

代码语言:javascript
复制
function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="dashboard" element={<Dashboard />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}

2.1.7 多路由集成到一个组件

在很多时候,我们还会看到多路由集成到一个组件。

代码语言:javascript
复制
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>
  );
}

2.2 API

除了上面的一些基本的使用方法外,React Router还提供了非常丰富的API,下面列举一些常见的:

2.2.1 Routers

  • BrowserRouter:浏览器router,web开发首选;
  • HashRouter:在不能使用browserRouter时使用,常见SPA的B端项目
  • HistoryRouter:使用history库作为入参,允许开发者在非 React context中使用history实例作为全局变量,标记为unstable_HistoryRouter,后续可能会被修改,不建议直接引用;
  • MemoryRouter:不依赖于外界(如 browserRouter的 history 堆栈),常用于测试用例;
  • NativeRouter:RN环境下使用的router,不作过多介绍;
  • Router:可以视为所有其他router的基类;
  • StaticRouter:Node环境下使用的router;

2.2.2 Components

  • Link:在react-router-dom中,Link被渲染为有真实href的<a/>标签,同时,Link to 支持相对路径路由;
  • NavLink:有“active”标的Link,尝被用于导航栏等场景;
  • Navigate:可以理解为被useNavigate包裹的组件,作用通Link类似;
  • Outlet:类似slot,向下传递route;
  • Routes & Route:URL变化时,Routes匹配出最符合要求的Routes渲染;

2.2.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中使用;

2.2.4 Utilities

  • createRoutesFromChildren :将转为route object形式;
  • createSearchParams:类似useSearchParams;
  • generatePath:将通配符和动态路由和参数转为真实path;
  • Location:用于hostory router,声明Location的interface;
  • matchPath:类似useMatch,返回匹配到的route path;
  • matchRoutes:返回匹配到的route 对象;
  • renderMatches:返回matchRoutes的react元素;
  • resolvePath:将Link to的值转为带有绝对路径的真实的path对象;

参考链接:https://reactrouter.com/en/6.6.1/docs/en/v6/routers/browser-router

三、 适配V6

3.1.1 去掉withRouter

withRouter的用处是将一个组件包裹进Route里面, 然后react-router的三个对象history,、location、match就会被放进这个组件的props属性中,可以实现对应的功能。下面是V5版本withRouter的使用方法。

代码语言:javascript
复制
import React from 'react'
import './nav.css'
import { NavLink, withRouter } from "react-router-dom"
class Nav extends React.Component{
    handleClick = () => {
        console.log(this.props);
    }
    render() {
        return (
            <div className={'nav'}>
                <span className={'logo'} onClick={this.handleClick}>xx电商</span>
                <li><NavLink to="/" exact>首页</NavLink></li>
                <li><NavLink to="/activities">动态</NavLink></li>
                <li><NavLink to="/topic">话题</NavLink></li>
                <li><NavLink to="/login">登录</NavLink></li>
            </div>
        );
    }
}
export default withRouter(Nav)

React Router的V6中,更多使用的是Hooks语法,所以只需要可以将类组件转为函数组件即可。

代码语言:javascript
复制
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;
}

3.1.2 树形结构里嵌套路由

由于和在V6版本中被移除,所以在V6版本的树形结构里嵌套路由需要做如下的修改。

V5版本写法:

代码语言:javascript
复制
<Switch>
  <Route path="/users" component={Users} />
</Switch>;


function Users() {
  return (
    <div>
      <h1>Users</h1>
      <Switch>
        <Route path="/users/account" component={Account} />
      </Switch>
    </div>
  );
}

V6版本写法:

代码语言:javascript
复制
<Routes>
  <Route path="/users/*" element={<Users />} />
</Routes>;


function Users() {
  return (
    <div>
      <h1>Users</h1>
      <Routes>
        <Route path="account" element={<Account />} />
      </Routes>
    </div>
  );
}

3.1.3 取消正则路由

V6版本有一个重要的细节是:取消正则路由。之所以取消正则路由,是因为如下几点原因:

  • 正则路由为V6版本的路由排序带来很多问题,比如,如果定义一个正则的优先级;
  • 正则路由占据了React Router近1/3的体积;
  • 正则路由能表达的,V6版本都支持;

例如,我们在V5版本中,在进行Route路径适配的时候可以直接使用正则,如下:

代码语言:javascript
复制
function App() {
  return (
    <Switch>
      <Route path={/(en|es|fr)/} component={Lang} />
    </Switch>
  );
}
function Lang({ params }) {
  let lang = params[0];
  let translations = I81n[lang];
  // ...
}

由于V6版本取消了正则路由,所以上面的代码需要改成如下方式:

代码语言:javascript
复制
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];
  // ...
}

四、React Router原理

与后端路由不同,前端网站都是单页面应用,要实现路由切换时不触发整个页面的刷新,就需要前端路由框架满足两个关键点。

  • 改变路径url时不触发页面刷新
  • 当url发生改变时会重新渲染url对应的界面

所以,我们谈React Router的原理,其实就是分析订阅和操作history堆栈、URL 与router匹配以及渲染router相匹配的UI的问题。

4.1 基本概念

在正式讲解之前,我们先看一下路由中的一些概念:

  • URL:地址栏中的URL;
  • Location:由React Router基于浏览器内置的window.location对象封装而成的特定对象;
  • Location State:代表Location的状态;
  • History Stack:浏览器保留的location堆栈数据,可以使用它进行返回操作;
  • History:一个object,它允许 React Router 订阅 URL 中的更改,并提供 API 以编程方式操作浏览器历史堆栈;
  • History Action :路由操作,包括POP、PUSH或者 REPLACE。
  • Segment :【/】字符之间的URL或 path pattern部分。例如“/users/123”有两个segment;
  • Path Pattern:用于URL与路由匹配的特殊字符。
  • Dynamic Segment:动态路径匹配;
  • URL Params: 动态段匹配的URL的解析值;
  • Router :使所有其他组件和hooks工作的有状态的最高层的组件;
  • Route Config:将当前路径进行匹配,通过排序和匹配创建一个树状的routes对象;
  • Route:具有 { path, element } 或 的路由元素;
  • Route Element: 也就是 , 读取该元素的 props 以创建路由;
  • Nested Routes: 由于路由可以有子路由,且每个路由通过segment来定义URL 的一部分,所以单个 URL 可以匹配树的嵌套“分支”中的多个路由。并且还可以通过outlet、relative links等实现自动布局嵌套;
  • Relative links:不以 / 开头的链接,继承渲染它们的最近路径。在无需知道和构建整个路径的情况下,就可以实现更深层的url macth;
  • Match:路由匹配 URL 时保存信息的对象;
  • Matches:与当前位置匹配的路由数组,此结构用于nested routes;
  • Parent Route:带有子路由的父路由节点;
  • Outlet: 匹配match中的下一个匹配项的组件;
  • Index Route :当没有path时,在父路由的outlet中匹配;
  • Layout Route: 专门用于在特定布局内对子路由进行分组;

4.2 history

React Router工作的前提是,它必须能够订阅浏览器history stack中的数据,并进行push、pop和replace操作。通过客户端路由(CSR),我们可以通过代码操纵浏览器历史记录栈。例如,我们可以编写代码来改变URL,而不需要浏览器向服务器发出请求的默认行为。

代码语言:javascript
复制
<a
  href="/contact"
  onClick={(event) => {
    // 阻止默认事件
    event.preventDefault();
    // push 并将 URL转想/contact
    window.history.pushState({}, undefined, "/contact");
  }}/>

以上代码会修改URL,但不会渲染任何UI的变化,如果我们需要修改页面UI,那么需要我们监听变化。

代码语言:javascript
复制
window.addEventListener("popstate", () => { 
 
});

但此类事件只在点击前进后退按钮才生效,对window.history.pushState 或者 window.history.replaceState无效。因此,React Router使用history对象来监听事件的变化,如POP、PUSH或者REPLACE。

代码语言:javascript
复制
let history = createBrowserHistory();
history.listen(({ location, action }) => {
   
});

在开发环境中,我们不需要关系history object,这些在React Router底层实现了,React Router提供监听history stack的变化,最终在URL变化时更新其状态,并重新渲染。

4.3 location

React Router 的location模块申明如下:

代码语言:javascript
复制
{
  pathname: "/bbq/pig-pickins",
  search: "?campaign=instagram",
  hash: "#menu",
  state: null,
  key: "aefz24ie"
}

pathname、search、hash大致等同于window.location一致,三者拼接起来就是URL。我们可以使用urlSearchParams来获取对应的search内容。

代码语言:javascript
复制
let location = {
  pathname: "/bbq/pig-pickins",
  search: "?campaign=instagram&popular=true",
  hash: "",
  state: null,
  key: "aefz24ie",
};


let params = new URLSearchParams(location.search);
params.get("campaign"); // "instagram"
params.get("popular"); // "true"
params.toString(); // "campaign=instagram&popular=true",

4.4 路由匹配

在初始渲染时,当历史堆栈发生变化时,React Router 会将位置与您的路由配置进行匹配,以提供一组要渲染的匹配项。比如,有下面一段

代码语言:javascript
复制
<Routes>
  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

那么它对应的routes如下,可以使用 useRoutes(routesGoHere)进行获取。

代码语言:javascript
复制
let routes = [
  {
    element: <App />,
    path: "/",
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "teams",
        element: <Teams />,
        children: [
          {
            index: true,
            element: <LeagueStandings />,
          },
          {
            path: ":teamId",
            element: <Team />,
          },
          {
            path: ":teamId/edit",
            element: <EditTeam />,
          },
          {
            path: "new",
            element: <NewTeamForm />,
          },
        ],
      },
    ],
  },
  {
    element: <PageLayout />,
    children: [
      {
        element: <Privacy />,
        path: "/privacy",
      },
      {
        element: <Tos />,
        path: "/tos",
      },
    ],
  },
  {
    element: <Contact />,
    path: "/contact-us",
  },
];

所以,我们在应用中申明的路由,可以匹配程如下的内容:

代码语言:javascript
复制
<Route path=":teamId" element={<Team/>}/>
//匹配为  
{
  pathname: "/teams/firebirds",  
  params: {
    teamId: "firebirds"  
  },
  route: {
    element: <Team />,
    path: ":teamId"
  }
}

由于routes是树状结构,因此一个单一的URL可以匹配所有的树中的“分支”。

4.5 渲染

会将位置与路由配置相匹配,得到一组匹配的内容,然后呈现一个React元素树。比如,有下面一段路由申明:

代码语言:javascript
复制
const root = ReactDOM.createRoot( document.getElementById("root"));
root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route index element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />
        </Route>
      </Route>
      <Route element={<PageLayout />}>
        <Route path="/privacy" element={<Privacy />} />
      </Route>
      <Route path="contact-us" element={<Contact />} />
    </Routes>
  </BrowserRouter>
);

如果要匹配“/teams/firebirds”路径,渲染的层级如下:

代码语言:javascript
复制
<App>
  <Teams>
    <Team />
  </Teams>
</App>

4.6 导航函数

在V6版本中,我们可以使用useNavigate钩子函数来导航到某个页面。

代码语言:javascript
复制
let navigate = useNavigate();
useEffect(() => {
  setTimeout(() => {
    navigate("/logout");
  }, 30000);
}, []);

不过,需要提醒的是,不要随意使用navigate,这样会增加程序的复杂性,推荐使用<Link>组件。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、简介
    • 1.1 SAP
      • 1.2 路由
        • 1.3 路由模式
        • 二、基本使用
          • 2.1 基础API
            • 2.1.1 配置路由
            • 2.1.2 Link
            • 2.1.3 Navigation
            • 2.1.4 获取路由参数
            • 2.1.5 嵌套路由
            • 2.1.6 兜底路由
            • 2.1.7 多路由集成到一个组件
          • 2.2 API
            • 2.2.1 Routers
            • 2.2.2 Components
            • 2.2.3 Hooks
            • 2.2.4 Utilities
            • 3.1.1 去掉withRouter
            • 3.1.2 树形结构里嵌套路由
            • 3.1.3 取消正则路由
        • 三、 适配V6
        • 四、React Router原理
          • 4.1 基本概念
            • 4.2 history
              • 4.3 location
                • 4.4 路由匹配
                  • 4.5 渲染
                    • 4.6 导航函数
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档