React Router 6 概述

  1. React Router 以三个不同的包发布到 npm 上,它们分别为:

    1. react-router: 路由的核心库,提供了很多的:组件、钩子。
    2. react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>等。
    3. react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>等。
  2. 与React Router 5.x 版本相比,改变了什么?

    1. 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。
    2. 语法的变化:component={About} 变为 element={<About/>}等。
    3. 新增多个hook:useParamsuseNavigateuseMatch等。
    4. 官方明确推荐函数式组件了!!!

准备工作

创建新项目:

1
npx create-react-app router6

目前create-react-app默认创建为React 18,先降到17版本:

1
npm install react@17.x react-dom@17.x --save

安装react-router-dom

1
npm i react-router-dom

若以上命令出现报错,则加后缀:--legacy-peer-deps

编写基本内容:

  1. 项目结构
    image.png

  2. index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import React from "react"
    import ReactDOM from "react-dom"
    import { BrowserRouter } from "react-router-dom"
    import App from "./App"

    ReactDOM.render(
    <BrowserRouter>
    <App />
    </BrowserRouter>,
    document.getElementById('root')
    )

    App.jsx声明函数式组件

一级路由

React Router 6中,<Route />component属性已替换为elementSwitch已弃用并替换为Routes,值得注意的是,在React Router 5中,用<Switch></Switch>包裹<Route />不是必须的,但在React Router 6中必须用<Routes></Routes>包裹<Route />

App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from 'react'
import { NavLink, Routes, Route } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'

export default function App() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 路由链接 */}
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Routes>
<Route path='/about' element={<About/>} />
<Route path='/home' element={<Home/>} />
</Routes>
</div>
</div>
</div>
</div>
</div>
)
}

GIF 2022-5-21 19-03-58.gif

重定向

React Router 6已将原有的Redirect删除,现在使用Navigate

只要<Navigate />组件被渲染,就会修改路径、切换视图

  • to属性:重定向地址
  • replace属性:false为push,true为replace

使用:嵌套在Route标签中使用

1
<Route path='/' element={<Navigate to='/about' />} />

需求:点击使sum+1并实现路由跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, {useState} from 'react'
import { Navigate } from 'react-router-dom'

export default function Home() {
const [sum, setSum] = useState(1)
return (
<>
<h3>我是Home的内容</h3>
{sum === 2 ? <Navigate to="/about" /> : <h4>当前sum的值是:{sum}</h4>}
<button onClick={() => setSum(2)}>点击sum+1</button>
</>
)
}

GIF 2022-5-24 19-52-53.gif

作用: 与<Link>组件类似,且可实现导航的“高亮”效果,与Router 5不同的是,不在使用activeClassName,而是如下所示:

1
2
3
4
5
6
7
<NavLink
to="login"
className={({ isActive }) => {
console.log('home', isActive)
return isActive ? 'base one' : 'base'
}}
>login</NavLink>

默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。

路由表

使用useRoutes Hook,根据路由表,动态创建<Routes><Route>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React from 'react'
import { NavLink, Navigate, useRoutes } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'

export default function App() {
// 根据路由表生成对应的路由规则
const element = useRoutes([
{
path: '/about',
element: <About/>
},
{
path: '/home',
element: <Home/>
},
{
path: '/',
element: <Navigate to="/about"/>
}
])

return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 路由链接 */}
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
{element}
</div>
</div>
</div>
</div>
</div>
)
}

嵌套路由

路由表写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* eslint-disable import/no-anonymous-default-export */
import { Navigate } from "react-router-dom"
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message"
import News from "../pages/News"

export default [
{
path: '/about',
element: <About />
},
{
path: '/home',
element: <Home />,
children: [
{
path: 'message', // 不要有斜线
element: <Message />
},
{
path: 'news',
element: <News />
},
]
},
{
path: '/',
element: <Navigate to="/about" />
}
]

Home组件:使用<Outlet />,当<Route>产生嵌套时,渲染其对应的后续子路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react'
import { NavLink, Outlet } from 'react-router-dom'

export default function Home() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
<NavLink className="list-group-item" to="news">News</NavLink> {/* 不能写斜线 */}
</li>
<li>
<NavLink className="list-group-item" to="message">Message</NavLink>
</li>
</ul>
{/* 指定路由组件呈现的位置 */}
<Outlet />
</div>
</div>
)
}

传递参数

params参数

  1. 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>

  2. 在路由表中声明接收参数:

    1
    2
    3
    4
    5
    6
    7
    8
    path: 'message', // 不要有斜线
    element: <Message />,
    children: [
    {
    path: 'detail/:id/:title/:content',
    element: <Detail />
    }
    ]
  3. 接收参数:const {id, title, content} = useParams()

Message组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useState } from 'react'
import { Link, Outlet } from 'react-router-dom'

export default function Message() {

const [messages] = useState([
{id: '001', title: 'message1', content: 'abcdefg'},
{id: '002', title: 'message2', content: 'hijklmn'},
{id: '003', title: 'message3', content: 'opqrst'},
{id: '004', title: 'message4', content: 'uvwxyz'},
])

return (
<div>
<ul>
{
messages.map((message) => {
return (
<li key={message.id}>
<Link to={`detail/${message.id}/${message.title}/${message.content}`}>{message.title}</Link>&nbsp;&nbsp;
</li>
)
})
}
</ul>
<hr />
{/* 指定路由组件的展示位置 */}
<Outlet />
</div>
)
}

Detail组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react'
import { useMatch, useParams } from 'react-router-dom'

export default function Detail() {
const {id, title, content} = useParams()
const x = useMatch('/home/message/detail/:id/:title/:content')
console.log(x)
return (
<ul>
<li>id: {id}</li>
<li>title: {title}</li>
<li>content: {content}</li>
</ul>
)
}

search参数

  1. 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
  2. 注册路由(无需声明,在路由表中正常注册即可)
  3. 接收参数:const [search, setSearch] = useSearchParams()

Detail组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react'
import { useLocation, useSearchParams } from 'react-router-dom'

export default function Detail() {
const [search, setSearch] = useSearchParams()
const id = search.get('id')
const title = search.get('title')
const content = search.get('content')
const x = useLocation()
console.log(x)
return (
<ul>
<li>
<button onClick={() => setSearch('id=005&title=阿巴阿巴&content=ohhhhhh')}>点击修改search参数</button>
</li>
<li>id: {id}</li>
<li>title: {title}</li>
<li>content: {content}</li>
</ul>
)
}

GIF 2022-5-27 22-23-06.gif

state参数

传递的内容不会在地址栏展示,与组件的状态(state)不同

  1. 路由链接(携带参数):

    1
    2
    3
    4
    5
    6
    7
    8
    <Link
    to='detail'
    state={{
    id: message.id,
    title: message.title,
    content: message.content
    }}>{message.title}
    </Link>
  2. 注册路由(无需声明,在路由表中正常注册即可)

  3. 接收参数:const {state: {id, title, content}} = useLocation()

  4. 备注:刷新也可以保留住参数,但清除浏览器数据后不可

编程式路由导航

需求:鼠标滑过时引起组件渲染,这时<Link/> <NavLink/> <Navigate/>都不能胜任

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import React, { useState } from 'react'
import { Link, Outlet, useNavigate } from 'react-router-dom'

export default function Message() {

const [messages] = useState([
{id: '001', title: 'message1', content: 'abcdefg'},
{id: '002', title: 'message2', content: 'hijklmn'},
{id: '003', title: 'message3', content: 'opqrst'},
{id: '004', title: 'message4', content: 'uvwxyz'},
])

const navigate = useNavigate()

function show(message) {
navigate('detail', {
replace: false, // 默认为false,可不写
state: {
id: message.id,
title: message.title,
content: message.content
}
})
}

return (
<div>
<ul>
{
messages.map((message) => {
return (
<li key={message.id}>
<Link
to='detail'
state={{
id: message.id,
title: message.title,
content: message.content
}}>{message.title}
</Link>&nbsp;&nbsp;
<button onClick={() => show(message)}>展示详情</button>
</li>
)
})
}
</ul>
<hr />
{/* 指定路由组件的展示位置 */}
<Outlet />
</div>
)
}

GIF 2022-5-27 22-54-09.gif

扩展:前进与后退

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react'
import { useNavigate } from 'react-router-dom'

export default function Header() {
const navigate = useNavigate()
function back() {
navigate(-1)
}
function forward() {
navigate(1)
}
return (
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={back}>👈后退</button>
<button onClick={forward}>前进👉</button>
</div>
</div>
)
}

其他的Hooks

useInRouterContext()

主要用于判断当前组件(一般组件和路由组件)是否处于路由的上下文环境中,即组件被BrowserRouterHashRouter包裹(包括子组件),不在路由的上下文环境中,即脱离了路由器的管理

返回一个布尔值,处于路由的上下文环境中时返回真。

useNavigationType()

返回当前的导航类型

  • POP:在浏览器中直接打开或刷新页面
  • PUSH
  • REPLACE

useOutlet()

用来呈现当前组件中渲染的嵌套路由

  • console.log(useOutlet())
  • 如果嵌套路由没有挂载,则输出null
  • 如果嵌套路由已经挂载,则展示嵌套的路由对象

useResolvedPath()

给定一个URL值,解析其中的pathsearchhash

console.log(useResolvedPath('/user?id=01&name=abc&se=hi#goto'))