React 前端导航

React Router V6 如何使用,与V5的区别

React Router V6 如何使用,与V5的区别

阅读本文后,你将快速掌握 react router v6 的基本使用并简单对比了与 React Route V5 版本的区别。

安装

npm install react-router-dom@6 --save

使用react脚手架创建一个 react + Ts 的 Demo 来演示,并且使用了 antd5 的组件:

// 创建 react 项目
npx create-react-app my-app --template redux-typescript
// 安装 antd5
npm install antd  --save 
// 安装react-router-dom
npm install react-router-dom@6 --save

常用组件和hooks

组件名 作用 说明
<BrowserRouter> 路由模式 history 路由模式
<HashRouter> 路由模式 hash 路由模式
<Routers> 一组路由 代替原有,所有子路由都用基础的Router children来表示
<Router> 基础路由 Router是可以嵌套的,解决原有V5中严格模式,后面与V5区别会详细介绍
<Link> 导航组件 在实际页面中跳转使用
<Outlet/> 自适应渲染组件 根据实际路由url自动选择组件
hooks名 作用 说明
useParams 返回当前参数 根据路径读取参数
useNavigate 返回当前路由 代替原有V5中的 useHistory
useOutlet 返回根据路由生成的element
useLocation 返回当前的location 对象
useRoutes 同Routers组件一样,只不过是在js中使用
useSearchParams 用来匹配URL中?后面的搜索参数

简单使用方法

启用全局路由模式

本文使用 BrowserRouter - history模式。URL采用真实的URL资源,每次路由变更其实都是一次 get 请求。 如图我们定义了一个路由地址为 '/home' 的首页 , 当我们在游览器地址输入 http://localhost:3000/home 时,可以看到我们向这个地址发起了一次 get 请求。
react router BrowserRouter
在项目的入口文件使用BrowserRouter来创建路由:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter, HashRouter } from 'react-router-dom';

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

使用 Routes、Route 配置路由分支

在项目里分别定义了两个组件 Home 和 GoodsList , 在App.tsx文件中使用 Route时 ,外层必须加上 Routes 组件,也就是 Routes -> Route 的组合配置路由分支,在v6版本中移除了v5中的 Switch 组件。

App.tsx

import React from 'react';
import logo from './logo.svg';
import './App.css';
import Home from './pages/Home';
import { Route, Routes } from 'react-router-dom';
import GoodsList from './pages/GoodsList';
import GoodsDetail from './pages/GoodsDetail';
import User from './pages/User';
import UserDetail from './pages/UserDetail';
import AddUser from './pages/AddUser';
import NotFound from './pages/NotFound';
function App() {
  return (
    <div className="App">
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="/home" element={<Home />}></Route>
        <Route path="/goodsList" element={<GoodsList />}></Route>
      </Routes>
    </div>
  );
}
export default App;

此时使用 "/" 、"/home" 访问页面时,页面渲染 Home 组件。
react router 路由分支

useNavigate hook

在Home组件使用 useNavigatehook,它返回一个函数帮助我们能够编程式导航。

类型定义:

declare function useNavigate(): NavigateFunction;

interface NavigateFunction {
  (
    to: To,
    options?: {
      replace?: boolean;
      state?: any;
      relative?: RelativeRoutingType;
    }
  ): void;
  (delta: number): void;
}

const navigate = useNavigate()
这个 navigate 函数上有两个签名

  1. 当使用to这个值时其接收的路由地址,例如 navigate('/home')
  2. 使用delta 时,将你想要放入历史堆栈的增量传递给历史堆栈。 例如 navigate(-1)

Home.tsx 使用:

import React from 'react';
import { useNavigate } from "react-router-dom";
import { MailOutlined } from '@ant-design/icons';
import {  MenuProps } from 'antd';
import { Menu } from 'antd';
import './index.css';
type MenuItem = Required<MenuProps>['items'][number];

...

const items: MenuProps['items'] = [
  getItem('菜单一', 'sub1', <MailOutlined />, [
    getItem('Item 1', 'g1', null, [getItem('商品列表', '1'), getItem('用户列表', '2')], 'group'),
    getItem('Item 2', 'g2', null, [getItem('Option 3', '3'), getItem('Option 4', '4')], 'group'),
  ]),
];

const Home: React.FC = () => {
  // 路由跳转方法
  const navigate = useNavigate()
  const onClick: MenuProps['onClick'] = (e) => {
    if (e.key === '1') {
      // 接收想要导航到的路由名称
      navigate(`/goodsList`);
    }
    if (e.key === '2') {
      navigate(`/user`);
    }
  };

  return (
    <div className='container'>
      <Menu
        onClick={onClick}
        style={{ width: 256 }}
        defaultOpenKeys={['sub1']}
        mode="inline"
        items={items}
      />
      <div className='content'>
        这是首页
      </div>
    </div>

  );
};

export default Home;

此时点击【首页】左侧菜单上的【商品列表】,跳转到商品列表页。
react router useNavigate

在商品列表页 GoodsList 通过使用 Link 组件 ,在其 to 属性上传递导航到的路由路径实现跳转。

GoodsList.tsx

import React from 'react';
import { Space, Table, Tag } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { Link } from 'react-router-dom';

...

const GoodsList: React.FC = () => {
  return (
    <div>
      <h2>商品列表页</h2>
      <Table columns={columns} dataSource={data} />
      <div>
        // 点击跳转到首页
        <Link to='/home'>首页</Link>
      </div>
    </div>
  )
};

export default GoodsList;

经过前面的组件和方法使用一个简单的路由demo就能够正常运行了

动态路由

我们在项目要新增一个 GoodsDetail 商品详情页,该页面是需要从商品列表页跳转过去,并且在URL地址上携带上商品名称,此时我们就可以使用动态路由实现

首先在 App.tsx 里对 GoodsDetail 组件进行动态路由的配置:

import GoodsList from './pages/GoodsList';
import GoodsDetail from './pages/GoodsDetail';
...

function App() {
  return (
    <div className="App">
      <Routes>
                ...
        <Route path="/goodsList" element={<GoodsList />}></Route>
        // 以 `:key 形式定义动态路由的key值`
        <Route path='/goods/:name' element={<GoodsDetail />}></Route>
      </Routes>
    </div>
  );
}
export default App;

在 GoodList 组件实现动态路由跳转:我们点击商品列表页的 Table 组件的【详情】按钮时,页面导航到商品详情页,并且将商品名称值携带过去。

...
const columns: ColumnsType<DataType> = [
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
    render: (text) => <a>{text}</a>,
  },
  {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  },
  {
    title: 'Action',
    key: 'action',
    render: (_, record) => (
      <Space size="middle">
        // 点击详情按钮,将商品名称拼接到路由地址上
          <Link to={`/goods/${record.name}`}>详情</Link>
      </Space>
    ),
  },
];

const GoodsList: React.FC = () => {
  return (
      ...
  )
};

export default GoodsList;

react router 动态路由

useParams hook 获取动态路由参数

在上面的动态路由实现过程中,我们从商品列表页跳转商品详情页时,路由地址写到了商品名称,我们可以通过useParams hook拿到。它返回一个包含动态路由 key-value 形式的对象。

GoodsDetail.tsx

import { Button } from 'antd';
import React from 'react'
import { useNavigate, useParams } from 'react-router-dom'

export default function GoodsDetail() {
  // 获取动态路路由参数
  const routeParams = useParams();
  console.log('动态路由参数', routeParams); // 动态路由参数 {name: '苹果'}
  
  const navigate = useNavigate();
  
  return (
    <div>
      <h2>商品详情页</h2>
      <div>商品名称: {routeParams.name}</div>
      <div style={{ margin: 20 }}> <Button type='primary' onClick={() => { navigate(-1) }}>
        返回商品页面页
      </Button>
      </div>
      <div style={{ margin: 20 }}> <Button onClick={() => { navigate('/home') }}>
        返回首页
      </Button>
      </div>
    </div>
  )
}

路由嵌套

路由嵌套是一个很强大的功能,可以减少冗杂的布局代码,降低布局的难度。如下使用:
定义一组关于 User 相关的嵌套路由
分别通过如下三种路径匹配:
● "/user"
● "/user/007"
● "/user/addUser"

App.tsx

...

function App() {
  return (
    <div className="App">
      <Routes>
         ...
        {/* 嵌套路由 */}
        <Route path='user' element={<User />}>
          <Route path=":id" element={<UserDetail />} />
          <Route path="addUser" element={<AddUser />} />
        </Route>
      </Routes>
    </div>
  );
}
export default App;

Outlet

使用Outlet组件渲染子组件
可以使用一个路由占位符,针对多匹配的路由位置进行复用,类似于vue router的路由插槽和Angular的router-outlet:

User.tsx

import { Button } from 'antd'
import { Link, Outlet } from 'react-router-dom'

export default function User(props: any) {
  return (
    <div>
      <h2>
        用户页面
      </h2>
      <p>
        <span style={{ marginRight: 20 }}>用户名称:憨憨</span>
        {/* 展示用户详情组件 */}
        <Link to={`/user/${'007'}`}>详情</Link>
      </p>
      <Button>
        {/* 展示新增用户组件 */}
        <Link to='/user/addUser'>新增用户</Link>
      </Button>
      <div>
        {/* Outlet 渲染子代路由的地方, 功能类似 Vue Router 里的 <router-view> 组件 */}
        <Outlet />
      </div>
    </div>
  )
}

访问 /user 时
react router outlet
点击 【详情】按钮访问 /user/007
react router outlet
点击【新增用户】按钮访问 /user/addUser
react router outlet

默认路由 (index 路由)

当一个父路由有多个子路由,且当前URL停留在父路由时,上面例子中,路由为'/user'时,outlet里就无法识别渲染),界面该如何渲染呢?你需要给父路由设置一个默认渲染的子路由。
加上index属性后就会默认渲染该路由下的组件。而且默认索引路由可以放在路由嵌套的任何一级中使用。

...

function App() {
  return (
    <div className="App">
      <Routes>
         ...
        {/* 嵌套路由 */}
        <Route path='user' element={<User />}>
           {/* 当路由为 /user 时,Outlet 里会默认展示  UserDetail 组件 */}
          <Route index element={<UserDetail />} />
          <Route path=":id" element={<UserDetail />} />
          <Route path="addUser" element={<AddUser />} />
        </Route>
      </Routes>
    </div>
  );
}
export default App;

react router 默认路由

...
// <User />
<div>
     ...
     <Link to={`${'007'}`}>详情</Link>
     ...
     <Link to='addUser'>新增用户</Link>
</div>

...
<Routes>
     <Route path='user' element={<User />}>
          <Route path=":id" element={<UserDetail />} />
          <Route path="addUser" element={<AddUser />} />
    </Route>
</Routes>

在上面嵌套路由的例子,我们将Link组件 to 属性里值写法变更
<Link to='/user/addUser'>新增用户</Link> => <Link to='addUser'>新增用户
在这种情况下,上述两个Link会连接到/user/007 和 /user/adduser 两个地址,这就是相对路由。

useSearchParams hook

useSearchParams hook 解析当前路径,返回location中的search;并可重新设置 search 并触发跳转。类似React 自带的 useState hook, 它也是返回一个包含两个值的数组。

实例:

import React from 'react'
import { useParams, useSearchParams } from 'react-router-dom'
import './index.css'

export default function UserDetail() {
     const queryParams = useParams(); 

  // 获取是地址栏上URL的 search 参数
  let [searchParams, setSearchParams] = useSearchParams();
  // 此时 http://localhost:3000/user/007?name=鲁迅Ï
  console.log(searchParams.get("name")); // 鲁迅

  return (
    <div className='box'>
      <h3>用户详情</h3>
      <div>用户id: {queryParams.id || ''}</div>
      <div>用户名称:{searchParams.get("name")}</div>
    </div>
  )
}

react router useSearchParams

404路由

当没有一个URL匹配时,就会查找是否配置了通用路由 path="*",他可以匹配任何形式的路由,当没有更精确的路由匹配时,就进入该路由的配置,该路由的优先级是最低的。

...

function App() {
  return (
    <div className="App">
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="/home" element={<Home />}></Route>
        ...
        {/* 关于NotFound类路由,可以用*来代替 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

export default App;

这是一个不存在的路由名称
react router 404路由

v5和v6的区别

v5 的用法可参考:React Router V5

组件层面上:

● 老版本路由采用了 Router Switch Route 结构,Router -> 传递状态,负责派发更新; Switch -> 匹配唯一路由 ;Route -> 真实渲染路由组件。
● 新版本路由采用了 Router Routes Route 结构,Router 为了抽离一 context; Routes -> 形成路由渲染分支,渲染路由;Route 并非渲染真实路由,而是形成路由分支结构。

使用层面上:

● 老版本路由,对于嵌套路由,配置二级路由,需要写在具体的业务组件中。
● 新版本路由,在外层统一配置路由结构,让路由结构更清晰,通过 Outlet 来实现子代路由的渲染,一定程度上有点类似于 vue 中的 view-router。
● 新版本做了 API 的大调整,比如 useHistory 变成了 useNavigate,减少了一些 API ,增加了一些新的 api

原理层面上:

● 老版本的路由本质在于 Route 组件,当路由上下文 context 改变的时候,Route 组件重新渲染,然后通过匹配来确定业务组件是否渲染。
● 新版本的路由本质在于 Routes 组件,当 location 上下文改变的时候,Routes 重新渲染,重新形成渲染分支,然后通过 provider 方式逐层传递 Outlet,进行匹配渲染。

声明:本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。邮箱:farmerlzj@163.com。 本站原创内容未经允许不得转载,或转载时需注明出处: 内容转载自: React前端网:https://qianduan.shop/blogs/detail/87

#react#router#v5#v6

相关推荐

react中实现markdown文件读取展示

react中实现markdown文件读取展示

umi实践问题汇总--持续更新

在使用umi的过程中所遇到问题的记录汇总