本文最后更新于: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
初始化路由表
React router6
支持组件式
与函数式
两种创建方式,由于官方推荐函数式
创建故暂时仅记录了函数式
的创建方式,对Vuer友好
新建一个router
文件夹来管理路由表
createBrowserRouter
API可以创建一个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
类,也可使用属性名为style
与className
的回调函数设置激活样式
回调函数可以接收一个对象,有isActive
与isPending
两个属性
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'
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
方法来实现重定向功能,一般在路由表的loader
或action
中使用
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'
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
钩子因用于函数式组件中。路由重定向因在loader
或action
中使用redirect
方法
调用useNavigate
函数返回一个函数,使用该函数就可以用js
实现路由的跳转(修改)
该函数接收一个string
或number
(-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 }
|