白筱汐

想都是问题,做都是答案

0%

react router v7 入门指南

React Router v7 入门指南

本教程基于 react router v7 版本,旨在快速实现一个 react router 的案例功能。

详细内容请查看 react router官网

快速启动一个 react 项目

使用 vite 快速创建一个 react 项目

1
pnpm create vite my-vue-app --template react

安装依赖

1
pnpm i

启动项目

1
pnpm dev

实现基础路由功能

安装 react router 库

1
pnpm i react-router

新建路由页面

在 src 里新建 pages 目录,里面新建 about.jsx,home.jsx,list.jsx, 各 react 元素可以随意返回一些内容,以供路由匹配展示。

内容类似下面这样:

1
2
3
4
5
6
7
8
// about.jsx
export default function About() {
return (
<div>
About Page
</div>
)
}

配置路由

修改 main.jsx 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createRoot } from 'react-dom/client'
import { BrowserRouter, Routes, Route } from 'react-router'
import './index.css'
import App from './App.jsx'
import Home from './pages/home.jsx'
import List from './pages/list.jsx'
import About from './pages/about.jsx'

createRoot(document.getElementById('root')).render(
<BrowserRouter>
<Routes>
<Route path='/' element={<App />}>
<Route index element={<Home />}></Route>
<Route path='list' element={<List />}></Route>
<Route path='about' element={<About />}></Route>
</Route>
</Routes>
</BrowserRouter>,
)

react router 有2种路由模式, BrowserRouterHashRouter

  • BrowserRouter: 使用现代浏览器的 History API(pushState 和 replaceState)来同步 URL 和应用程序状态。
  • HashRouter:使用 URL 中的哈希片段(#)来保存路由状态。

使用 Routes 包括 Route 组件,可以创建我们的路由。Route 组件的 path 属性代表路由绑定的路径,element 代表路由要展示的元素内容。

有一个 Route 没有 path 属性,但是它出现 index 属性,代表它是父路由的默认子路由,也就是说当我们访问“根路径” 就能看到 Home 元素。

完成路由跳转和页面展示

修改 main,jsx 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Link } from 'react-router'
import { Outlet } from 'react-router'
import './App.css'

function App() {

return (
<div>
<h1>react router demo v6</h1>
<nav>
<Link to="/" style={{ marginRight: '15px' }}>首页</Link>
<Link to="/list" style={{ marginRight: '15px' }}>列表</Link>
<Link to="/about" style={{ marginRight: '15px' }}>关于</Link>
</nav>

<Outlet />
</div>
)
}

export default App

使用 Link 组件的 to 属性可以配置路由的跳转地址,Outlet 组件用于展示当前路由匹配的页面(组件)。

重新启动项目,现在你可以看到如下图展示的内容,点击 home、about、list,可以看到路由的底部展示的渲染的内容发生了变化,并浏览器地址也对应更新。

页面效果

至此,我们已经完成了基础的路由配置和、跳转以及页面的展示。

匹配 404 页面

通常当我们访问一个不存在的页面的地址的时候,需要展示一个 404 页面。通常这个页面不属于根页面的子路由。

在 src/pages 目录里面新建一个 404.jsx 文件,内容如下:

1
2
3
4
5
export default function NotFound() {
return (
<div>Not Found 404</div>
)
}

修改 main.jsx 路由配置,就能完成我们需要的功能了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {createRoot} from 'react-dom/client'
import {BrowserRouter, Routes, Route} from 'react-router'
import './index.css'
import App from './App.jsx'
import Home from './pages/home.jsx'
import List from './pages/list.jsx'
import About from './pages/about.jsx'
import NotFound from "./pages/404.jsx";

createRoot(document.getElementById('root')).render(
<BrowserRouter>
<Routes>
<Route path='/' element={<App/>}>
<Route index element={<Home/>}></Route>
<Route path='list' element={<List/>}></Route>
<Route path='about' element={<About/>}></Route>
</Route>
{/* 匹配 404 页面 */}
<Route path='*' element={<NotFound/>}></Route>
</Routes>
</BrowserRouter>,
)

现在在浏览器访问 http://localhost:5173/1 , 页面就会展示 “Not Found 404” 了。

获取路由参数 params 和 query

获取路由 params

配置 list 路由的子路由 list/id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 省略相关重复代码
createRoot(document.getElementById('root')).render(
<BrowserRouter>
<Routes>
<Route path='/' element={<App/>}>
<Route index element={<Home/>}></Route>
<Route path='list' element={<List/>}>
{/*新增子路由,list/id*/}
<Route path=':id' element={<ListDetail/>}></Route>
</Route>
<Route path='about' element={<About/>}></Route>
</Route>
<Route path='*' element={<NotFound/>}></Route>
</Routes>
</BrowserRouter>,
)

在 src/pages 目录新建 list-detail.jsx 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {useParams} from "react-router";

export default function ListDetail() {

// 获取 params 参数对象
const params = useParams()
console.log(params)

return (
<div>
List Detail Page id: {params.id}
</div>
)
}

修改 list.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
import {Link, Outlet} from "react-router";

export default function List() {
const data = [
{
id: 1,
name: '订单1'
},
{
id: 2,
name: '订单2'
}
];

return (
<div>
{ data.map( (item) => <Link style={{marginRight: '15px'}} to={ `/list/${item.id}`} key={item.id} >
detail {item.name}
</Link> ) }

{/* 展示子页面的内容 */}
<Outlet></Outlet>
</div>
)
}

现在重新启动项目,你会看到下面这样的界面:

页面效果

点击 列表 下面的 detail 订单 1 , 可以看到展示了 List Detail Page id: 1。

使用 useParams() 方法可以获取到 params 对象,里面包含了相关的参数信息。

获取路由 query

修改 list.jsx 文件,新增一个携带 query 参数的 Link 组件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 省略部分重复内容...
return (
<div>
{ data.map( (item) => <Link style={{marginRight: '15px'}} to={ `/list/${item.id}`} key={item.id} >
detail {item.name}
</Link> ) }

{/* 新增一个携带 query 参数的 Link 组件 */}
<Link to='/list/3?type=a' >detail type=a</Link>

{/* 展示子页面的内容 */}
<Outlet></Outlet>
</div>
)

修改 list-detail.jsx 文件,加入获取路由 query 参数的代码,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import {useParams, useSearchParams} from "react-router";

export default function ListDetail() {
const params = useParams()
console.log(params)

// searchParams 是一个 URLSearchParams 对象,具体请参考 MDN
let [searchParams] = useSearchParams();
console.log(searchParams)
console.log(searchParams.get('type')) // 获取具体 query 参数

return (
<div>
List Detail Page id: {params.id}
</div>
)
}

点击 新增的 Link 组件,跳转到 list-detail 页面,展示内容为 “List Detail Page id: 3”。

页面效果

查看控制台可以发现我们获取去到了路由的 query 参数 type=a; 这里说明一下 useSearchParams() 方法返回的数组中,第一个元素是 一个 URLSearchParams 实例; URLSearchParams 接口定义了一些实用的方法来处理 URL 的查询字符串。URLSearchParams 接口 MDN

如果需要获取更多路由信息,可以使用 useLocation 方法:

1
2
let location = useLocation();
console.log(location)

location 内容

编程式导航

有些时候我们需要使用 js 主动去触发页面的跳转,可以使用方法 useNavigate

params 和 query 参数你可以直接写在跳转链接里面, 例如 ‘/list/1?type=a’,不在演示。

1
2
3
4
5
// 编程式导航
const navigate = useNavigate();
const backToHome = () => {
navigate('/')
}

重定向

1
redirect("/login")

自定义导航守卫

在项目开发的过程中,一般我们会有这样的需求:有些页面可以随意访问,但是部分页面必须用户登录之后才能访问。我们可以给路由配置一个白名单,如果当前访问页面的路径在白名单里面,就能正常访问。反之,如果访问的页面需要权限,用户必须登录之后才能正常访问。不能访问的页面当用户触发跳转时,重定向到登录页面。在登录完成之后,在回到之前的页面。

封装一个校验路由权限并且重定向的组件。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
// eslint-disable-next-line react/prop-types
export default function RequireAuth({children}) {
const whiteList = ["/", "/about"]

const { pathname } = useLocation();

// 不需要权限的页面
const notRequiredAuth = whiteList.includes(pathname);

// 访问的页面在白名单里,或者用户拥有权限 hasToken,直接跳转。否则重定向到到 login
return (hasToken || notRequiredAuth ? children : <Navigate to="/login" replace state={pathname} />)
}

接下来我们只要使用该组件包裹我们的路由组件就好了

1
2
3
<RequireAuth>
<Link to="/list" style={{marginRight: '15px'}}>列表</Link>
</RequireAuth>

Data Mode

react router v7 官网现在出现了3种模式,Framework Mode、Data Mode、Declarative Mode。

  • Framework Mode:
    • 强依赖 React Router Data APIs(loader、action、errorElement 等)
    • 文件即路由:如 app/routes/dashboard.tsx 就是 /dashboard
    • 默认 SSR 支持(如 Remix)
    • 路由定义抽象于文件系统,不用手动写 createBrowserRouter
  • Data Mode:
    • 使用 createBrowserRouter 配置路由并定义 loader、action 等函数。
    • 强调路由组件初始化前就完成数据加载
  • Declarative Mode:
    • 使用 “Routes” “Route” 等 JSX 方式声明路由,数据处理在组件内部完成。(传统模式)

Framework Mode 架构模式更适合于 SSR 应用,对于一般的管理后台,把路由根据文件来配置有点不太方便,管理后台通常有路由权限的问题,所以不建议使用此模式。

Declarative Mode 也就是传统的声明组件的方式配置路由,大部分使用过 react router 的用户都能接受,但是有点繁琐不够灵活。

Data Mode 支持配置路由的 loader、action 等,灵活性较好,可以学习这种模式,方便以后过度到架构模式

Data Mode 处理路由权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ProtectedLayout.tsx
import { Outlet } from 'react-router-dom';

export function ProtectedLayout() {
return <Outlet />;
}

export function authLoader() {
const token = localStorage.getItem('token');
if (!token) {
throw redirect('/login');
}
return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
path: '/',
element: <RootLayout />,
children: [
{
element: <ProtectedLayout />,
loader: authLoader,
children: [
{
path: 'dashboard',
element: <DashboardPage />,
},
{
path: 'settings',
element: <SettingsPage />,
},
],
},
]
}