引入
如果我们要切换一个网页,从0加载一个新的网页,消耗时间也消耗资源,而route可以让react应用变成一个SPA,在URL变化的时候,不刷新页面,只是替换掉页面里的部分组件,从而实现更快的更新响应。
核心原理
React Router内部使用了一个叫 history 库(由 React Router 团队维护)来统一管理这些操作(还有一个老版路由这里就不做介绍了)。当你使用 <BrowserRouter> 时,它会:创建一个 history 对象(基于 window.history)。在组件树顶层通过 Context 把这个 history 对象和当前 location(URL 信息)传递下去。监听 popstate 事件(用户点后退/前进时触发),并通知 React 重新渲染匹配的组件
它的 <Link> 组件实际上会被渲染成:
<a href="/about">关于我们</a>
但 React Router 给这个 <a> 添加了一个 点击事件处理器:
function handleClick(event) {
event.preventDefault(); /*阻止浏览器跳转/刷新*/
history.push('/about'); /*使用 History API 更新 URL*/
}
代码实现
安装
npm install react-router-dom
接入
首先我们要把整个APP用一个标签包裹,让给他拥有路由能力
import App from './App.jsx'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { StrictMode } from 'react'
const root = createRoot(document.getElementById('root'))
root.render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
)
抽象路由模块
我们要想Vue一样创建一个router文件夹,实现嵌套路由。
我们来简单创建几个组件
src/
│
├── pages/
│ ├── Login.jsx # 登录页,一级路由
│ ├── Layout.jsx # 布局页,包含导航栏,一级路由
│ ├── Dashboard.jsx # 看板,二级路由
│ ├── Article.jsx # 文章,二级路由
│ └── NotFound.jsx # 404页
│
└── router/
└── index.jsx # 路由配置文件
然后我们来配置一下路由
src/router/index.jsx
import { useRoutes } from 'react-router-dom'
// 引入组件
// (实际项目中推荐使用 lazy load 懒加载,这里为了简单直接引入)
import Login from '../pages/Login'
import Layout from '../pages/Layout'
import Dashboard from '../pages/Dashboard'
import Article from '../pages/Article'
import NotFound from '../pages/NotFound'
// 定义路由表
const routes = [
{
path: '/login',
element: <Login />
},
{
path: '/',
element: <Layout />,
children: [
{
/*访问'/'时自动渲染dashboard */
index: true,
element: <Dashboard />
},
{
path: 'article',
element: <Article />
}
]
},
{
/*所有没定义的路径*/
path: '*',
element: <NotFound />
}
]
export default function RouterView() {
const element = useRoutes(routes)
return element
}
挂载路由
import RouterView from './router'
function App() {
return (
<div className="App">
<RouterView />
</div>
)
}
export default App
嵌套路由
在React Router v6中我们需要使用<Outlet />
要实现嵌套路由,父组件必须写明子路由渲染在哪里,
src/pages/Layout.jsx
import { Link, Outlet } from 'react-router-dom'
function Layout() {
return (
<div style={{ border: '2px solid blue', padding: '20px' }}>
<h1>我是通用布局 (Layout)</h1>
<nav style={{ marginBottom: '20px', borderBottom: '1px solid #ccc' }}>
<Link to="/" style={{ marginRight: '10px' }}>看板</Link>
<Link to="/article">文章管理</Link>
<Link to="/login" style={{ marginLeft: '20px', color: 'red' }}>退出登录</Link>
</nav>
<div style={{ border: '2px dashed green', padding: '10px' }}>
<Outlet />
</div>
</div>
)
}
export default Layout
导航和传参
看了上面的代码就要问了,ttdr,Link是什么东西,这就是一个导航
说白了就是不同URL的跳转,这里React 提供了两种方式,一种是Link,另一种是通过代码逻辑比如说useNavigate来实现(登陆成功后跳转回首页)
既然有跳转,我们上面也是在写一个文章管理,那么我要跳转到某一篇文章,我们就要对他们进行编号,这个时候我们跳转的时候就需要带着参数(id)去进行跳转,我要看ttdr写的前端相关的文章,我们就需要加上筛选条件,比如说/book?type=front。这就是我们下面要干的事情
核心函数
react router提供了三个核心钩子函数
useNavigate()—— “老司机”
这是一个动作函数。它给你返回一个 Maps 函数,你调用它,页面就跳走了,它底层调用了浏览器 History API 的 pushState,把新的 URL 推进历史栈里,同时通知 React Router 更新组件。
useParams()—— “身份证读取器”
专门读取 URL 路径里形如 :id 的动态部分,Router 会把当前的 URL 和你定义的 path 进行正则匹配,提取出冒号后面的变量。定义路由 path: '/article/:id',访问 /article/42,useParams() 就会返回 { id: '42' }。怎么跟Gin里面的c.Param(“key”)一样
useSearchParams()—— “筛选器解析器”
专门读取 URL 里 ? 后面的内容,它返回一个数组 [searchParams, setSearchParams]。searchParams 是一个对象(类似于 Map),你可以用 .get('page') 拿到值。这也非常像 React 的 useState,因为它还给了一个 set 函数让你修改查询参数。怎么Gin里面的c.Query(“key”)一样
看完这个似乎一切都闭环了!
代码实现
我们先来改个路由
{
path: '/',
element: <Layout />,
children: [
{
/*访问'/'时自动渲染dashboard */
index: true,
element: <Dashboard />
},
{
path: 'article',
element: <Article />
},
{
path: 'article/:id',
element: <ArticleDetail />
}
]
},
实现跳转,并且传参
Dashboard.jsx
import { useNavigate } from "react-router-dom"
function Dashbord() {
const navigate =useNavigate()
const goToArticle = (id) =>{
navigate(`article/${id}`)
}
const goToArticleWithQuery = (id) =>{
navigate(`/article/${id}?from=dashboard&highlight=true`)
}
return (
<div>
<h2>文章列表</h2>
<ul>
<li>
<span>ID: 1001</span>
<button onClick={() => goToArticle(1001)}>查看详情</button>
</li>
<li>
<span>ID: 2002</span>
<button onClick={() => goToArticleWithQuery(2002)}>
查看详情
</button>
</li>
</ul>
</div>
)
}
export default Dashbord
ArticleDetail.jsx
详细页
import { useParams, useSearchParams } from 'react-router-dom'
function ArticleDetail() {
const params = useParams()
const [searchParams] = useSearchParams()
const searchId = searchParams.get('id')
const source = searchParams.get('from')
const isHighlight = searchParams.get('highlight')
return (
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<button onClick={() => navigate(-1)}>返回上一页</button>
<h1>文章详情页</h1>
<div style={{ background: '#f0f0f0', padding: '10px' }}>
<h3>Params:</h3>
<p>当前文章 ID: <strong>{params.id}</strong></p>
<p><i>可以拿着这个 ID 去向服务器请求文章的具体内容。</i></p>
</div>
<div style={{ background: '#e6f7ff', padding: '10px', marginTop: '10px' }}>
<h3>Search:</h3>
<p>来源: {source || '未知来源'}</p>
<p>是否高亮: {isHighlight === 'true' ? '是' : '否'}</p>
</div>
</div>
)
}
export default ArticleDetail
Params vs Search:该选谁?
| 特性 | Params (路径参数) | Search (查询参数) |
|---|---|---|
| 外观 | /article/1001 | /article?id=1001 |
| 语义 | 资源本身 (Resource) | 资源的修饰 (Modifier) |
| 必选性 | 通常是必须的(没有ID就没法显示文章) | 通常是可选的(没有排序也能显示列表) |
| 路由表 | 需要在路由表中预定义 (path: '/:id') | 不需要定义,随时可以在 URL 后面加 |
路由别名设置
懒得按../,有没有更简单的方法,有的有的兄弟。
我们用vite 构建我们要先告诉vite怎么识别别名,我们打开vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
})
vite知道了但是vscode还不知道,我们在根目录新建一个jsconfig.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}








