🟡React Router6

本文最后更新于:2024年4月25日 上午

引言

为什么需要router

  • SPA希望可以记录用户操作(分享链接)
  • 减少服务器请求(跳转时不要刷新页面)
  • 页面管理更方便

丐版router

在使用react-router-dom前尝试实现一个丐版的单层router

src/layout/index.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
37
38
39
40
41
import { Home } from '@/pages/Home'
import { Article } from '@/pages/Article'
import { User } from '@/pages/User'
import { useState } from 'react'

export const Layout = () => {
const [params, setParams] = useState(window.location.pathname.split('/'))

const handlePush = (url = '') => {
history.pushState(null, '', url)
setParams(url.split('/'))
}

return (
<>
<header>
<h1>Hello React🌏</h1>
</header>

<nav>
<ul>
<li>
<a onClick={() => handlePush('/')}>Home</a>
</li>
<li>
<a onClick={() => handlePush('/user')}>User</a>
</li>
<li>
<a onClick={() => handlePush('/article')}>Article</a>
</li>
</ul>
</nav>

<main>
{params[1] === '' && <Home />}
{params[1] === 'user' && <User />}
{params[1] === 'article' && <Article />}
</main>
</>
)
}

上面router的实现主要依靠H5history.pushState()API可以更改路径而不刷新页面

存在问题:

  • 需要自己维护router路径参数
  • 多级的路由参数不好维护
  • 根据路径参数写组件渲染逻辑很麻烦

react-router-dom帮我解决了这个大麻烦😜

还给予了我一堆的强大功能

安装

vite创建react-js一把梭

安装react-router-dom

1
pnpm i react-router-dom

初始化路由表

React router6支持组件式函数式两种创建方式,由于官方推荐函数式创建故暂时仅记录了函数式的创建方式,对Vuer友好

新建一个router文件夹来管理路由表

createBrowserRouterAPI可以创建一个history模式的路由,这里用别名createRouter为了方便以后可能更改路由模式

createBrowserRouter接受两个参数

  • routes:路由配置数组
  • options:配置对象

route的配置支持

  • loader: 在路由跳转前执行一些操作
  • action:
  • path: 路由匹配路径
  • index: 设置true为默认路由
  • errorElement: 路由出错时的兜底组件
  • children: 子路由,配置同routes

src/router/index.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
import { createBrowserRouter as createRouter } from 'react-router-dom'
import Layout from '@/layout'
import Home from '@/pages/Home'
import User from '@/pages/User'
import Article from '@/pages/Article'

const routes = [
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: <Home />,
},
{
path: 'user',
element: <User />,
},
{
path: 'article',
element: <Article />,
},
],
},
]

const router = createRouter(routes, {
basename: '',
})

export default router

使用懒加载

React.lazy只支持default导出函数式组件

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
import { createBrowserRouter as createRouter } from 'react-router-dom'
import { Suspense, lazy } from 'react'
import Layout from '@/layout'

const Home = lazy(() => import('@/pages/Home'))
const User = lazy(() => import('@/pages/User'))
const Article = lazy(() => import('@/pages/Article'))

const Lazy = (Component, props) => (
<Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</Suspense>
)

const routes = [
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: Lazy(Home),
},
{
path: 'user',
element: Lazy(User),
},
{
path: 'article',
element: Lazy(Article),
},
],
},
]

const router = createRouter(routes, {
basename: '',
})

export default router

使用路由组件

挂载router

使用RouterProvider组件添加激活路由

组件接收两个props

  • router: 创建的路由表
  • fallbackElement:组件加载时的提示组件(同Suspense)

src/main.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createRoot } from 'react-dom/client'
import { StrictMode } from 'react'
import { RouterProvider } from 'react-router-dom'
import router from '@/router'

const app = createRoot(document.getElementById('root'))

app.render(
<StrictMode>
<RouterProvider router={router} fallbackElement={<div>Loading</div>} />
</StrictMode>
)

路由链接与组件渲染位置

react-router-dom提供Link组件来跳转路由

Link组件类似a标签可用于路由的切换

  • to属性定义跳转路径
  • to属性不加/为在当前路径上追加字符串(可以使用相对路径)

不要使用a标签来跳转,常规a标签会刷新页面,向服务端发送请求

Outlet组件告知应在

src/layout/index.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
import { Link, Outlet } from 'react-router-dom'

export default function Layout() {
return (
<>
<header>
<h1>Hello React🌏</h1>
</header>

<nav>
<ul>
<li>
<Link to={'/'}>Home</Link>
</li>
<li>
<Link to={'user'}>User</Link>
</li>
<li>
<Link to={'article'}>Article</Link>
</li>
</ul>
</nav>

<main>
<Outlet />
</main>
</>
)
}

导航链接激活样式

Link组件无法方便的判断当前路由路径是否与Link匹配

NavLink组件用法同Link组件

另外当路径与NavLink的路径严格匹配时会为该标签添加active类,也可使用属性名为styleclassName的回调函数设置激活样式

回调函数可以接收一个对象,有isActiveisPending两个属性

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
53
54
55
56
57
58
59
60
61
62
import { NavLink, Outlet } from 'react-router-dom'
import './index.css'

export default function Layout() {
const activeStyleHandler = ({ isActive, isPending }) => {
const activeLinkStyle = {
color: 'red',
fontWeight: 'bold',
}

return isActive ? activeLinkStyle : {}
}

const activeClassHandler = ({ isActive, isPending }) => {
return isActive ? 'link-active' : 'link-inactive'
}

return (
<>
<header>
<h1>Hello React🌏</h1>
</header>

<nav>
<ul>
<li>
<NavLink
style={activeStyleHandler}
className={activeClassHandler}
to={'/'}
>
Home
</NavLink>
</li>
<li>
<NavLink
style={activeStyleHandler}
className={activeClassHandler}
to={'user'}
>
User
</NavLink>
</li>
<li>
<NavLink
style={activeStyleHandler}
className={activeClassHandler}
to={'article'}
>
Article
</NavLink>
</li>
</ul>
</nav>

<main>
<Outlet />
</main>
</>
)
}

NavLink还可设置aria-label提升无障碍功能

获取路径参数

先给Article页面添加一个动态参数id用于区分不同的文章页

src/router/index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const routes = [
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: Lazy(Home),
errorElement: <div>404</div>,
},
{
path: 'user',
element: Lazy(User),
},
{
+ path: 'article/:id',
element: Lazy(Article),
},
],
},
]

react-router-dom提供了很多的钩子函数用于获取与操作路由

使用useParams返回一个params对象(只有动态路径有params),也可解构获取所需参数,再用路径参数做一些操作

src/pages/Article/index.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
37
38
39
40
41
42
43
44
45
46
import { useParams, Link } from 'react-router-dom'
import { useEffect, useState } from 'react'
import { getArticleById } from '@/api/article'

const LOADING = {
title: 'Loading...',
content: 'Loading...',
}

export default function Article() {
const params = useParams()

const [article, setArticle] = useState(LOADING)

useEffect(() => {
setArticle(LOADING)
;(async function () {
const { id } = params
const article = await getArticleById(+id)

setArticle(article)
})()
}, [params?.id])

return (
<div>
<h2>📝Article</h2>
<nav>
<ul>
{new Array(5).fill(0).map((_, index) => (
<li key={index}>
<Link to={`/article/${index + 1}`}>article {index + 1}</Link>
</li>
))}
</ul>
</nav>
<article>
<header>
<h3>☘️{article.title}</h3>
</header>
<section>{article.content}</section>
</article>
</div>
)
}

Route loader

获取API数据在useEffect中是React18不推荐的,还记得路由表配置有一个loader选项吗,这时就可以把获取数据的内容放在loader中,让代码更加的优雅😍

loader属性接受一个回调函数({params, request}) => {}

回调函数可以获取params参数与一个对该页面的GET请求实例

src/router/index.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
import { articleLoader } from '@/pages/Article'
// 已省略部分无关code

const routes = [
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: Lazy(Home),
errorElement: <div>404</div>,
},
{
path: 'user',
element: Lazy(User),
},
{
path: 'article/:id',
element: Lazy(Article),
loader: articleLoader,
},
],
},
]

组件中使用useLoaderData钩子即可获取到loader函数的返回值

src/pages/Article/index.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
import { Link, useLoaderData } from 'react-router-dom'
import { getArticleById } from '@/api/article'

export const articleLoader = async ({ params }) => {
const article = await getArticleById(+params.id)
return article
}

export default function Article() {
const article = useLoaderData()

return (
<div>
<h2>📝Article</h2>
<nav>
<ul>
{new Array(5).fill(0).map((_, index) => (
<li key={index}>
<Link to={`/article/${index + 1}`}>article {index + 1}</Link>
</li>
))}
</ul>
</nav>
<article>
<header>
<h3>☘️{article.title}</h3>
</header>
<section>{article.content}</section>
</article>
</div>
)
}

编程式导航

编程式导航即使用js来操作路由跳转,如登出自动跳转至登录页等

redirect

上一个例子中可以访问/article:id的路径查看文章

但使用Article链接到路径/article时会无法匹配到任何路由而导致报错

现在希望到/article路径时可以随机跳转到一篇文章/article/:id的路径

react-router-dom提供了redirect方法来实现重定向功能,一般在路由表的loaderaction中使用

src/router/index.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
import { createBrowserRouter as createRouter, redirect } from 'react-router-dom'

// 省略部分无关code......

const routes = [
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: Lazy(Home),
errorElement: <div>404</div>,
},
{
path: 'user',
element: Lazy(User),
},
{
path: 'article',
loader: () => {
const randomId = Math.floor(Math.random() * 5) + 1
return redirect('/article/' + randomId)
},
},
{
path: 'article/:id',
element: Lazy(Article),
loader: articleLoader,
},
],
},
]

useNavigate

下面用useNavigate钩子仿写一个重定项组件,当然useNavigate钩子因用于函数式组件中。路由重定向因在loaderaction中使用redirect方法

调用useNavigate函数返回一个函数,使用该函数就可以用js实现路由的跳转(修改)

该函数接收一个stringnumber(-1后退一页)参数用于指定跳转位置

另外可接收一个

src/components/base/Redirect.jsx

1
2
3
4
5
6
7
8
9
10
import { useNavigate } from 'react-router-dom'
import { useEffect } from 'react'

export default function Redirect({ to }) {
const navigate = useNavigate()
useEffect(() => navigate(to), [])

return null
}


🟡React Router6
https://qingshaner.com/ReactRouter/
作者
清山
发布于
2022年10月22日
许可协议