React Router v6

334 阅读6分钟

1 什么是React-Router?

许多现代网站实际上是由一个页面组成的,它看起来像多个页面,是因为它们包含呈现为单独页面的组件,即单页面+多组件。这些网站通常被称为SPA(single-page web application,单页web应用)。每个单页web应用其实是一系列的 JS 文件,当用户请求网站时,网站返回一整个(或一系列)的 js 文件和 HTML,而当用户在某个页面内点击时,你需要告诉浏览器怎么加载另一个页面地址。单页应用中通常只有一个 index.html 文件的,所以浏览器自带的 <a> 链接并不能用来做单页应用的跳转,因此你需要一个在 React 中的路由实现。

然而 React 框架本身是不带路由功能的,因此如果你需要实现路由功能让用户可以在多个单页应用中跳转的话,就需要使用 React-Router。React-Router是React体系的一个重要部分,通过管理URL,实现组件的切换和状态的变化。

React-Router经过多个版本的迭代,现在已经到了v6。v4和v5的Router使用差别不大,但v5和v6的差别很大。

2 使用React-Router

假设我们现在已经写好了多个页面,项目文件结构如下:

image.png

我们想要的效果是当url为“/”、/product、/pricing和其他时,分别渲染HomePage、Product、Pricing和PageNotFound页面。实现步骤如下:

step1 安装

React-Router不是React核心库的一部分,因此首先需要将此库安装到你的React项目中:

npm i react-router-dom@6

step2 导入核心组件

你需要从 react-router-dom 包中导入 BrowserRouter,Routes,Route 和 Link:

(你可以选择手动导入,也可以借助代码编辑器的快捷键直接导入)

import { BrowserRouter, Routes, RouteLink } from 'react-router-dom';  

step3 使用BrowserRouter组件包围整个应用

BrowserRouter组件是React Router库提供的一种路由管理组件,它使用HTML5的history API来管理应用程序的路由。当你的用户前进后退时,history 这个库会记住用户的历史记录,这样需要跳转时可以直接操作。

BrowserRouter适用于客户端渲染的应用程序,它可以在浏览器中通过JavaScript动态地处理路由。这意味着你的应用可以在不重新加载页面的情况下进行路由导航,提供更快速和流畅的用户体验。

使用BrowserRouter时,需要将其作为React应用的顶层组件包裹,通常放置在应用的根组件中。它只能包含一个子元素,因此通常使用一个根路由组件(如Routes)来包裹具体的路由配置。

Routes组件:

  • 用来包裹所有的Route标签,在其中匹配一个路由。React-Router6.x不允许Router组件单独存在。(否则会报错:A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .)
  • 注意:React-Router5.x采用的是Switch组件,6.x改用Routes组件
//App.jsx
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";   //暂时还没用到Link组件

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
       ....
      </Routes>
    </BrowserRouter>
  );
};

export default App;

step4 使用Route组件指定路由

Route组件:

  • 用于路径的配置,需指明两个属性path和element
  • path属性:用于设置匹配到的路径
  • element属性:匹配路径应渲染的组件
  • 注意:React-Router6不再支持5.x中的component和exact属性,默认情况下,路由会使用完全匹配来进行渲染
//App.jsx
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";   //暂时还没用到Link组件
import HomePage from "./pages/HomePage";    //导入外部组件
import PageNotFound from "./pages/PageNotFound";
import Pricing from "./pages/Pricing";
import Product from "./pages/Product";

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />}></Route>
        <Route path="product" element={<Product />}></Route>
        <Route path="pricing" element={<Pricing />}></Route>
        <Route path="*" element={<PageNotFound />}></Route>
      </Routes>
    </BrowserRouter>
  );
};

export default App;

step5 添加Link/NavLink标签

在html中实现超链接跳转需要使用a链接,但它会导致整个页面重新加载。React-Router为我们提供了Link和NavLink组件,可以在不重新加载页面的情况下实现页面跳转

Link标签用于指定跳转,使用to属性指定路由地址,表示要跳转到哪儿去(to属性在DOM中会被渲染成a标签的href属性)。

<Link to="/">HomePage</Link>

这行代码被渲染成原生JavaScript的a标签元素:

<a href="/">HomePage</a>

我们新建一个可重用组件NavPage.jsx表示导航栏:

image.png

在NavPage.jsx中规定每个链接的跳转路由,如点击Home则跳转路由“/”,在App.jsx中我们曾使用Route组件规定路由“/”渲染HomePage.jsx组件。

import React from "react";
import { Link } from "react-router-dom";

const NavPage = () => {
  return (
    <nav>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/pricing">Pricing</Link>
        </li>
        <li>
          <Link to="/product">Product</Link>
        </li>
      </ul>
    </nav>
  );
};

export default NavPage;

上述组件渲染出如下视图:

image.png

往往我们希望当用户停留在某个视图时,该视图对应路由在导航栏上显示突出,这时需要将上述代码中的Link标签改成NavLink标签,这样当用户停留在某视图时,对应标签会多一个类active,在css文件中更改active类显示即可。

3 React Router6中引入的hooks

3.1 useLocation 获取当前页路径

在React Router6中,提供了一个hook,专门用来获取当前路径。比如我们希望当用户跳转到/about页面时会显示用户现在的位置、从哪个页面跳转过来。

首先需要导入useLocation这个钩子,然后仿照如下代码就可以获得当前位置。

import { useLocation } from "react-router-dom";

const About = () => {
  const location = useLocation();
  const { from, pathname } = location;
  return (
    <>
      now you are in {pathname}, and you are from {from} page
    </>
  );
};
export default About;

 3.2 useNavigate 实现页面跳转

首先需要导入useNavigate这个hook,调用这个hook,他返回一个函数,这个函数可以控制跳转到哪个页面。

import { useNavigate } from "react-router-dom";

const Login = () => {
  const navigate = useNavigate();
  return (
    <>
      login
      <button
        onClick={() => {
          navigate("/about");
        }}
      >
        跳转到关于页面
      </button>
    </>
  );
};
export default Login;

 4 嵌套路由

现在页面存在一级路由:

  • /app—>渲染组件AppLayout

我们希望在它的基础是实现二级路由:

  • /app/cities—>渲染City组件
  • /app/counties—>渲染Country组件
  • /app/form->渲染Form组件

配置二级路由,需要:

  1. 在AppLayout组件中定义路由嵌套关系
const App = () => {
  return (
    <CitiesProvider>
      <BrowserRouter>
        <Routes>
          ...
          <Route path="app" element={<AppLayout />}>
            <Route path="cities" element={<CityList />} />
            <Route path="countries" element={<CountryList />} />
            <Route path="form" element={<Form />} />
          </Route>
          ...
        </Routes>
      </BrowserRouter>
    </CitiesProvider>
  );
};
  1. 使用 Outlet 组件添加二级路由出口
const AppLayout = () => {
  return (
    <div className={styles.app}>
      <h1>layout</h1>
      <Outlet />
    </div>
  );
};

5 默认显式二级路由

浏览器首次加载页面时显示的路径都为/,有些情况下我们希望可以默认展示二级路由,如展示/board界面。这时需要去掉原本的Route标签中的path属性,改为添加index属性。

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />}>
          <Route path="board" element={<Board />}></Route>
          <Route index element={<Article />}></Route>
        </Route>
        <Route path="/login" element={<Login />}></Route>
      </Routes>
    </BrowserRouter>
  );
}

6 集中式路由配置

集中式路由配置,就是用一个数组统一把所有的路由对应关系写好,替换本来的Routes组件。

import { BrowserRouter, Routes, Route, useRoutes } from 'react-router-dom'

import Layout from './pages/Layout'
import Board from './pages/Board'
import Article from './pages/Article'
import NotFound from './pages/NotFound'

// 1. 准备一个路由数组 数组中定义所有的路由对应关系
const routesList = [
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        element: <Board />,
        index: true, // index设置为true 变成默认的二级路由
      },
      {
        path: 'article',
        element: <Article />,
      },
    ],
  },
  // 增加n个路由对应关系
  {
    path: '*',
    element: <NotFound />,
  },
]

// 2. 使用useRoutes方法传入routesList生成Routes组件
function WrapperRoutes() {
  let element = useRoutes(routesList)
  return element
}

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        {/* 3. 替换之前的Routes组件 */}
        <WrapperRoutes />
      </BrowserRouter>
    </div>
  )
}

export default App