# react-router-dom 是什么?
我们都知道 Vue 中官方钦定的路由跳转库就是 vue-router,而在 React 当中则是 react-router-dom 这个库。它们长得很像,用法也有共通之处。
react-router-dom
是一个用于在 React 应用中实现路由功能的库。它允许你建立一个单页面应用(SPA)的路由系统,通过这个系统,用户可以导航到应用的不同部分,而无需重新加载页面。这对于创建快速响应的用户界面非常有帮助。 react-router-dom
提供了一系列组件和 Hooks,使得在应用中添加路由和处理导航变得简单直观。
不过和 vue-router 不同的是,这个库提供了很多的 ReactDOM 组件以供用户直接使用以实现不同需求的路由跳转,而我们在使用 vue-router 的时候可能仅仅只是用到 router-view 和 router-link 两种组件,加上我们的编程式路由跳转,即可完成大部分的路由跳转任务。并且这个库也提供了很多 React 特色的 Hooks ,方便开发者进行编程式跳转。
# 核心组件
-
<BrowserRouter>
:这个组件使用 HTML5 历史 API(pushState
,replaceState
和popstate
事件)来保持 UI 和 URL 的同步。通常在你的应用最外层使用。 -
<Route>
:这个组件是路由的核心,它根据 URL 的路径渲染对应的界面组件。你可以将其看作是条件渲染的一种,当路径匹配时渲染某个组件。 -
<Switch>
(在 v5 中)/<Routes>
(在 v6 中):这些组件用于包裹一组<Route>
组件,但只渲染与当前路径匹配的第一个<Route>
。<Routes>
是react-router-dom
v6 中的新特性,替换了 v5 的<Switch>
。 -
<Link>
和<NavLink>
:这些组件允许你在应用内创建导航链接。使用<Link>
创建基本的跳转链接,而<NavLink>
则用于需要在当前激活的路由链接上添加样式的场景。
# Hooks
react-router-dom
v5.1+ 引入了几个有用的 Hooks,使得在函数组件中处理路由变得更加方便:
-
useHistory
(在 v6 中被useNavigate
替代):允许你访问历史实例,用于编程式导航(如跳转和后退)。 -
useLocation
:返回当前 URL 的 location 对象,你可以用它来获取当前路径名、查询字符串等。 -
useParams
:让你可以访问到当前路由参数的 Hook,非常适用于获取动态片段(如用户 ID)。 -
useRouteMatch
:用于匹配当前 URL。它可以让你获取有关当前匹配路由信息的数据。 -
useNavigate
(在 v6 中):提供了一种方法来改变当前位置,执行导航操作,如前进、后退或跳转到新的路径。
# react-router 初始化
react-router 的初始化一般分为以下:
- 安装 react-router-dom 库。
- 在 src 目录下新建
router/index.js
,内部编写路由跳转的所有逻辑,在最末尾进行导出。 - 在根目录下的
index.js
进行 router 的引入与注册。
# 安装
运行以下命令进行安装:
npm install react-router-dom |
# 编写路由逻辑
在 src/router/index.js
中,先引入 createBrowserRouter
后,调用传入路由配置数组,它会返回一个路由对象。把这个路由对象导出即可。
路由配置对象中由于需要指定规定路由需要渲染的组件,因此需要在最上面导入需要渲染的组件。
以下是一个配置示例:
import { createBrowserRouter } from "react-router-dom"; | |
import About from "@/pages/About"; | |
import Article from "@/pages/Article"; | |
const router = createBrowserRouter([ | |
{ | |
path: "/about", | |
element: <About />, | |
}, | |
{ | |
path: "/article", | |
element: <Article />, | |
}, | |
]); | |
export default router; |
我在此处引入了 About
和 Article
两个 JSX 组件,将它们作为路由数组的 element
属性的值传入。 path
属性则对应了要在哪个路由路径下渲染你指定的组件。
编写完成后,使用默认导出进行导出。
# 引入 router 并注册
在 src/index.js
中引入编写好的 router 以及 RouterProvider
组件,把原本的 App 组件用它替换掉,然后把 router 作为这个组件的 router 参数进行传入,即可完成注册。
import React from "react"; | |
import ReactDOM from "react-dom/client"; | |
import "./index.css"; | |
import reportWebVitals from "./reportWebVitals"; | |
// 路由相关 | |
import router from "./router"; | |
import { RouterProvider } from "react-router-dom"; | |
const root = ReactDOM.createRoot(document.getElementById("root")); | |
root.render( | |
<React.StrictMode> | |
<RouterProvider router={router} /> | |
</React.StrictMode> | |
); | |
reportWebVitals(); |
之后访问项目的时候,使用在路由数组中配置的路径访问就可以访问到对应的组件了。
# react-router 路由跳转
在 react-router 中,和 vue-router 类似,路由跳转也分为两种: 声明式路由 和 编程式路由 。
# 声明式路由
就是使用这个库提供的 <Link />
组件进行路由的跳转。这个组件在被 React 编译解析的时候,会被渲染成一个 a 标签。
使用方法:
import { Link } from "react-router-dom"; | |
const Layout = () => { | |
return ( | |
<div> | |
<Link to="/">面板</Link> | |
<Link to="/about">关于</Link> | |
</div> | |
); | |
}; | |
export default Layout; |
从上面这个例子可以看出,引入组件后,在 to 后面传你要跳转的路径字符串即可。中间部分可以填任意的 html 元素或者 ReactDOM,表示点击了这个元素之后就会触发跳转到 to 指定的路由。
to 里面还可以传对象,使用 pathname 属性指定路由字符串、state 属性指定要传递的数据。 无论什么数据类型,都可以在这个 state 里面进行传递,一般用来传递对象数据。
import { Link } from "react-router-dom"; | |
const MyComponent = () => { | |
const user = { | |
name: "张三", | |
age: 30, | |
}; | |
return ( | |
<Link | |
to=<!--swig0--> | |
> | |
点击查看用户信息 | |
</Link> | |
); | |
}; |
# 编程式路由
主要是使用这个库提供的 useNavigate
这个 Hook 来实现的。
import { useNavigate } from "react-router-dom"; | |
const Login = () => { | |
const navigate = useNavigate(); | |
return ( | |
<div> | |
我是登录页 | |
{/* 编程式的写法 */} | |
<button onClick={() => navigate("/article")}>跳转到文章页</button> | |
</div> | |
); | |
}; | |
export default Login; |
首先在这整个组件上面引入 useNavigate
这个钩子,然后在组件函数的 return 上面调用方法拿到 navigate,之后在想要触发路由跳转的元素上在对应的触发方法上加上 navigate 的函数回调即可。这个函数传入的也是路由字符串,触发后可以跳转到指定的地址。
# react-router 路由传参
整个 react 项目中,需要通过路由传参的场景主要有:
- 查询参数
- 动态路由参数
- 对象
# 查询参数 - searchParams
查询参数是在路由的最末尾,以 ?
标识参数起始位置,以 &
来分隔参数的一种形式。
# 参数传递
在路由跳转中,我们可以看出无论是编程式还是声明式路由导航,都是传递路由指定的字符串进行跳转的。我们的参数传递也是类似,直接将需要传递的参数拼接到路由字符串的末尾即可,react-router-dom 会自动地帮我们传递并解析参数。
import { Link, useNavigate } from "react-router-dom"; | |
const Login = () => { | |
const navigate = useNavigate(); | |
return ( | |
<div> | |
我是登录页 | |
<button onClick={() => navigate("/article?id=1001&name=jack")}> | |
searchParams传参 | |
</button> | |
</div> | |
); | |
}; | |
export default Login; |
使用 Link 组件的时候也是一样,在 to 的路由字符串后面加上符合格式的参数列表就可以。
# 参数接收
如果想要在路由数组中指定路由的组件中获取到当前路由对应的查询参数,需要使用 useSearchParams
这个钩子来进行获取。
import { useSearchParams } from "react-router-dom"; | |
const Article = () => { | |
const [params] = useSearchParams(); | |
const id = params.get("id"); | |
const name = params.get("name"); | |
return ( | |
<div> | |
我是文章页{id}-{name} | |
</div> | |
); | |
}; | |
export default Article; |
和其他的 hook 的使用方法一样,也是在 return 上方获取,使用 [] 来获取 params,之后使用 get
方法,传入对应的参数名称拿到对应参数的值。
# 动态路由参数 - params
形如 /article/100/jack
的 url,这边的 100 和 200 就是所谓的动态路由参数。
那么 react-router 怎么知道这个 100 和 200 对应的是什么呢?这需要在 src/router/index.js
里面对相应的路由 path 进行配置。比如我这边想要把这个 100 对应为 id,把 jack 对应为 name,可以修改路由:
{ | |
path: '/article/:id/:name', | |
element: <Article /> | |
}, |
这就是跟 vue-router 一模一样的操作。
# 参数传递
传递参数和 searchParams 是一样的,也是通过字符串拼接的方式进行直接的传递,此处不多赘述。
# 参数接收
动态路由参数的接收需要使用到 useParams
这个 hook。
import { useParams } from "react-router-dom"; | |
const Article = () => { | |
const params = useParams(); | |
const id = params.id; | |
const name = params.name; | |
return ( | |
<div> | |
我是文章页{id}-{name} | |
</div> | |
); | |
}; | |
export default Article; |
使用方法和 useSearchParams
稍有区别,它不需要用数组来接,而且返回的就是单纯的对象,使用 .属性
的方式就可以直接取出其值。
# 对象
一般路由比较少传对象,不过也是有场景需要使用到的。
# 参数传递
第一种方式就是上面讲的在 Link 组件中通过 to 中传递对象,这个对象的 state 属性可以传递任意形式的数据。
第二种方式是借助 history.push()
方法,同样也是通过 state 传,不过这属于编程式导航了。
const MyComponent = ({ history }) => { | |
const user = { | |
name: "张三", | |
age: 30, | |
}; | |
const navigateToUser = () => { | |
history.push({ | |
pathname: "/user", | |
state: { user }, | |
}); | |
}; | |
return <button onClick={navigateToUser}>查看用户信息</button>; | |
}; |
# 参数接收
在目标组件中,通过 location
对象的 state
属性来访问传递的参数:
import { useLocation } from "react-router-dom"; | |
const UserComponent = () => { | |
const location = useLocation(); | |
const { user } = location.state || {}; // 防止 state 未定义 | |
return ( | |
<div> | |
用户名: {user?.name} | |
<br /> | |
年龄: {user?.age} | |
</div> | |
); | |
}; |
# react-router 嵌套路由
嵌套路由指的是在一级路由的基础之上,还有进一步的页面分类需要跳转。
具体点的例子,比如一个 React 项目有文章页还有个人中心页,这个文章页就是展示文章列表,这个个人中心页里面还有很多分页:个人资料、个人文章列表、个人收藏页等等。那么,二级路由就可以用来处理这个个人中心页下面的子分页,使得路由结构井然有序。
# 二级路由配置
二级路由的配置和 vue-router 还是很相似(我感觉是不是 vue-router 有借鉴 react-router-dom),是在原本的路由数组 item 的 path 和 element 属性下面,又加上一个名为 children 的属性,它还是一个数组,内部还是路由数组的 item,只不过想要访问这个二级路由的组件,需要加上它的父路由的 path 前缀再加上这个 child 的 path,才能够访问到。
import Layout from "../page/Layout"; | |
import About from "../page/About"; | |
import { createBrowserRouter } from "react-router-dom"; | |
const router = createBrowserRouter([ | |
{ | |
path: "/main", | |
element: <Layout />, | |
children: [ | |
{ | |
path: "about", | |
element: <About />, | |
}, | |
], | |
}, | |
]); | |
export default router; |
比如这样配置,访问 About 组件就得通过 /main/about
来访问。
出了路由配置文件本身的配置,还需要配置对应的父组件,在这里就是 Layout 组件,需要告诉它你在哪里渲染传入的 About 组件。这个是通过 Outlet
组件来实现的。
import { Link, Outlet } from "react-router-dom"; | |
const Layout = () => { | |
return ( | |
<div> | |
我是一级路由layout组件 | |
<Link to="/">面板</Link> | |
<Link to="/about">关于</Link> | |
{/* 配置二级路由的出口 */} | |
<Outlet /> | |
</div> | |
); | |
}; | |
export default Layout; |
比如这边就是在最末尾的地方放了 Outlet
组件,在解析的时候会根据路由配置把这个组件换成配好的 About 组件。
# 默认二级路由
默认二级路由指的就是访问某个配了二级路由的一级路由的时候,指定哪个二级路由对应的组件默认进行渲染,不需要在路由后面加上对应的二级路由字符串才能够访问。这个是通过将路由数组 item 的 index 属性设置为 true 来实现的。设置了之后,也不用加 path 了。
import Layout from "../page/Layout"; | |
import Board from "../page/Board"; | |
import About from "../page/About"; | |
import { createBrowserRouter } from "react-router-dom"; | |
const router = createBrowserRouter([ | |
{ | |
path: "/main", | |
element: <Layout />, | |
children: [ | |
// 设置为默认二级路由 一级路由访问的时候,它也能得到渲染 | |
{ | |
index: true, | |
element: <Board />, | |
}, | |
{ | |
path: "about", | |
element: <About />, | |
}, | |
], | |
}, | |
]); | |
export default router; |
比如此处,直接访问 /main
,就会自动的显示出 Board 组件。
# 404 页面配置
当网页的 url 不能通过 router 中设置的路由数组匹配到对应的 path 时,会访问不到这个网页。那么可以使用 *
做一个兜底操作,当访问 404 时显示一个更美观的自定义组件,显示更人性化的提醒,顺便可以加一些用户的互动跳转回首页等。
import NotFound from "../page/NotFound"; | |
import { createBrowserRouter } from "react-router-dom"; | |
const router = createBrowserRouter([ | |
// 其他的路由配置 | |
{ | |
path: "*", | |
element: <NotFound />, | |
}, | |
]); | |
export default router; |
# 两种路由模式
# history
一般我们比较常用的就是 history 的路由模式,react-router 中我们常用的 createBrowserRouter
函数也就是创建 history 模式的路由使用的。
在采用 history 路由模式时,浏览器通过监听 popstate
事件来触发组件渲染。 popstate
事件在浏览器历史记录发生变化时触发,例如用户点击浏览器的前进或后退按钮时。这种方式是 HTML5 History API 的一部分,允许 web 应用在不重新加载页面的情况下更改浏览器的 URL。
HTML5 History API 提供了几个关键的函数来实现无刷新页面跳转:
history.pushState()
: 添加一个新的状态到浏览器的历史记录堆栈中。history.replaceState()
: 用一个新的状态替换当前的状态,不会在历史记录堆栈中添加新记录。history.back()
,history.forward()
,history.go()
: 允许程序化地移动历史记录堆栈中的指针,相当于用户点击了后退、前进或跳转到特定页面。
# hash
hash 模式我们知道就是在路由后面加一个 #
来标识这个符号后面的路由都是哈希值。react-router-dom 中采用 createHashRouter
这个函数来创建一个哈希路由。
采用 hash 路由模式时,浏览器主要通过监听 hashchange
事件来触发组件渲染。Hash 路由是基于 URL 的 hash(即 URL 中 #
符号后面的部分)来实现的。当 hash 发生变化时(例如,用户点击含有锚点(anchor)链接的 <a>
标签,或者程序通过 JavaScript 更改 location.hash
),浏览器不会向服务器发送请求,但会触发 hashchange
事件。