带你使用 React 路由 react-router-dom v6

1,109 阅读6分钟

前言

在浏览器中页面的跳转是根据地址栏 url 的变化的,从一个页面跳转到另一个页面,这个是属于多页面应用(MPA)。除了多页面,还有另一种情况,单页面应用(SPA)。

单页面应用只有一个完整的页面,url 的变化不会刷新页面,而是页面内容的局部变化。

路由

在 React 项目中路由也是较为重要的部分,我们可以用它来管理 URL,实现页面组件切换。在 router v6相比之前的 v3、v4、v5版本,v6吸收了之前版本的经验并进行了改进,相对于之前一些方法、属性也不尽相同,也相对复杂了一点。

使用

我们可以去安装 react-router-dom,它专门在 react 项目中实现一个 SPA 应用。

npm i react-router-dom

安装完成后查看 package.json 文件可以看到

image.png

上图可以看到安装的是 router v6 版本, v6 版本跟 v5 版本有些差异,移除了一些组件和修改一些属性,接下来使用和介绍的都是 v6 版本的

例子

index.js

React 的路由的实现有两种:一种是 browser history —— <BrowserRouter>,一种是 hash history ——<HashRouter>

从 react-router-dom 导入 BrowserRouter 组件,用来包裹 App 组件。

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
      <BrowserRouter>
        <App />
      </BrowserRouter>
  </React.StrictMode>
);

App.js

  • NavLink,该组件可以根据 to 属性跳转对应路由组件
  • Routes,和 Route 配合使用,用来包裹 Route 组件
  • Route,根据 element 属性传入的组件,给组件匹配于 path 值,当其路径与当前 path 值匹配时,则呈现该传入的组件
  • Navigate,只要被渲染,就会修改路径,切换视图
// App.js
import { NavLink, Routes, Route, Navigate } from 'react-router-dom';
import Home from "./pages/home/home"
import My from "./pages/my/my"

function App() {
  return (
    <div>
      <ul>
        <li>
          <NavLink to="/home">Home</NavLink>
        </li>
        <li>
          <NavLink to="/my">my</NavLink>
        </li>
      </ul>
      <Routes>
        <Route path="/home" element={<Home />}></Route>
        <Route path="/my" element={<My />}></Route>
        <Route path="/" element={<Navigate to="/home" />}></Route>
      </Routes>
    </div>
  );
}
export default App

home.js 创建 home 路由组件

function Home(props) {
  return (
    <div>
      <p>Hello, home!</p>
    </div>
  );
}
export default Home

my.js 创建 my 路由组件

function My() {
  return (
    <div>
      <p>Hello, my!</p>
    </div>
  );
}
export default My

这样我们就可以实现一个简单的路由切换了

image.png

路由表

其中这一段代码可以抽离出来,制定路由表

<Routes>
    <Route path="/home" element={<Home />}></Route>
    <Route path="/my" element={<My />}></Route>
    <Route path="/" element={<Navigate to="/home" />}></Route>
</Routes>

创建 routers 文件夹 ,新建 index.js 文件

import { Navigate } from "react-router-dom"
import Home from '../pages/home/home'
import My from '../pages/my/my'

export const router = [
  {
    path: '/home',
    element: <Home />
  },
  {
    path: '/my',
    element: <My />
  },
  {
    path: '/',
    element: <Navigate to="/home" />
  },
]

使用路由表,在其中需要调用 useRoutes 并传入已经配置好的路由表,生成相当于 routes 以及 route 的组件结构,可以直接利用 {} 去进行包裹使用

// App.js
import { NavLink, useNavigate, useRoutes } from 'react-router-dom';
import { routers } from './routers'

// 根据路由表生成对应的路由规则
const element = useRoutes(routers)

// 直接在 dom 结构中使用
{element}

路由跳转

Link 组件

import { Link } from 'react-router-dom';
<Link to="/my">Link</Link>

NavLink 组件

import { NavLink } from "react-router-dom"; 
<NavLink to="/my">my</NavLink>

useNavigate 钩子函数

调用 useNavigate 函数去获得 navigate,传入路由 path 就可以切换到对应路由

import { // BrowserRouter, useNavigate, } from "react-router-dom";
const navigate = useNavigate(); 
navigate("/my");

参数传递

当我们进行路由跳转的时候需要携带一些参数,然后去到另一个路由利用参数进行一些操作,像一些跳转到详情页,传递的就是文章的id值,利用这id值查询出相应的数据等等情景。传参的方法有 params、search这几种。

useParams

在url后面拼接参数,进行动态传参,显示在地址栏上,刷新页面后参数不丢失

// 路由配置
{
  path: '/my/:id',
  element: <My />
},

// 路由跳转
<li>
  <NavLink to="/my/18">my</NavLink>
</li>

// 路由组件接收,使用 useParams
import { useParams } from 'react-router-dom';
function My() {
  let params = useParams()
  console.log(params)
  return (
    <div>
      <p>Hello, my!</p>
    </div>
  );
}
export default My

由 useParams 获取到路由跳转传递的参数

image.png

?

可选参数,代表该参数可传可不传,不传的话也不会报错。

通常来说一旦url寻找不到对应路由地址会发生错误,从而渲染不出该组件。

// 路由配置
{
  path: '/my/:id?',
  element: <My />
},

* 通配符

可以在后面匹配任何字符

// 路由配置
{
  path: '/my/*',
  element: <My />
},

useSearchParams

在url后面用 ? 拼接参数,参数形式使用 key=value 方式,多个参数使用 & 进行拼接,进行动态传参,显示在地址栏上,刷新页面后参数不丢失

// 路由跳转,不用更改路由表
<li>
  <NavLink to="/my?age=18&name=小明">my</NavLink>
</li>

// 路由组件接收参数 useSearchParams
import { useSearchParams } from 'react-router-dom';
function My() {
  let [search] = useSearchParams()
  let age = search.get("age")
  let name = search.get("name")
  console.log(search, age, name)
  return (
    <div>
      <p>Hello, my!</p>
    </div>
  );
}
export default My

二级路由通过 useSearchParams 接收到的数据如下

image.png

useLocation

// 路由跳转
import { useNavigate } from 'react-router-dom';

const My = ()=> {
  const navigatea = useNavigate()
  const goHome = ()=> {
    navigatea('/home', {
      state: {
        id: 1, name: '张三'
      }
    })
  }
  return (
    <div>
      <div>my 页面</div>
      <Button type='dashed' onClick={goHome}>按钮</Button>
    </div>
  )
}
// 路由组件接收参数
import { useLocation } from 'react-router-dom';

const Home = ()=> {
const location = useLocation()
console.log('跳转', location.state)

useLocation 可以获取到当前页面的路径、查询参数和hash值等信息。

image.png

路由嵌套

在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。

在一级路由定义 children 数组,里面用于配置二级路由,也就是进行路由嵌套。

// 配置
const router = [
  {
    path: '/home',
    element: <Home />,
    children: [
      {
        path: 'header',
        element: <Header />
      }
    ]
  },
  {
    path: '/my',
    element: <My />
  },
  {
    path: '/',
    element: <Navigate to="/home" />
  },
]

要注意的是在一级路由组件里需要有<Outlet />去进行占位,在页面排放位置。

// home.js home路由使用
import { Outlet } from 'react-router-dom';
function Home() {
  
  return (
    <div>
      {/* 嵌套路由出口 */}
      <Outlet />
      <p>Hello, home!</p>
    </div>
  );
}
export default Home

路由懒加载

  • Suspense,Suspense组件 支持传入 fallback 属性,作为动态加载模块完成前组件渲染的内容。而该组件里包含为需要渲染的内容。
  • lazy,支持动态引入组件。
import { Navigate } from "react-router-dom"
import My from '../pages/my/my'
import { lazy, Suspense } from "react"
const Home = lazy(()=> import('../pages/home/home.js'))
const router = [
  {
    path: '/',
    element: <Navigate to="/home" />
  },
  {
    path: '/home',
    // element: <Home />,
    element: (<Suspense fallback={<div>Loading...</div>}>
      <Home />
    </Suspense>),
  },
  {
    path: '/my',
    element: <My />,
  },
]

export default router

也可以给全部路由添加上懒加载

// ./routes/index
import { lazy } from "react"

const Home = lazy(()=> import('@/pages/home/home.js'))
{
    path: '/home',
    element: <Home />,
},

// app.js
import { useRoutes } from 'react-router-dom';
import router from './routes/index';
import { Suspense } from "react"

let elementRouter = useRoutes(router)

<Suspense fallback={<div>Loading...</div>}>
    {elementRouter}
</Suspense>

路由守卫

新建 RouterAuth.js文件

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

export default function RouterAuth({ children }) {
  // 做相应的判断
  const token = localStorage.token
  if(token){
      return <>{children}</>
  }else{
      return <Navigate to="/my"></Navigate>
  }
}

使用

import RouterAuth from './routerAuth'

{
    path: '/home',
    element: (<Suspense fallback={<div>Loading...</div>}>
      // 使用导出的 RouterAuth 包裹需要判断的路由
      <RouterAuth><Home /></RouterAuth>
    </Suspense>),
},

总结

优点

  • 加快页面响应速度,降低了对服务器的压力,因为单页应用只有第一次会加载完整的页面,切换内容只需请求该内容的数据,不用重新请求完整的页面

  • 更好的用户体验,运行更加流畅,因为局部的更新请求的资源少,加载速度快

缺点

  • 不利于 SEO ,因为 SEO 是根据首次请求的页面进行排名的,当路由的切换是监测不到的
  • 因为是单页面应用,所以初次渲染首屏加载的内容会比较多,加载较慢