之前在工作中一直使用的是 Vue Router
,不久前接触到了 React Router
的 v6
版本;在学习过程中,发现两者还是有不少相似的地方。
这里将这些异同点简单做个比较与汇总,希望能通过这种类比的方式,帮助和我一样的新手快速掌握 React Router
的基础知识点 (顺便复习复习 Vue Router
)。
下列文章的
Vue Router
版本为 v4.x;React Router
版本为 v6.4。
创建路由实例
vue
在 vue
中主要使用 createRouter
来创建一个可以被 vue
应用使用的 router
实例:
// 1. 从 vue-router 中导入 createRouter 函数用于创建路由实例
// 以及 createWebHistory 函数配置历史模式
import { createRouter, createWebHistory } from 'vue-router'
// 2. 创建一个路由实例
const router = createRouter({
// 3. 配置历史模式 history 或 hash,这里是 history 模式
history: createWebHistory(),
// 4. 传入路由配置
routes: [
//...
]
})
react
而在 react
中,用于创建路由实例的则是 createBrowserRouter
函数:
// 1. 导入 createBrowserRouter 函数用于创建路由实例
// createBrowserRouter 使用的是 history 模式
import { createBrowserRouter } from "react-router-dom";
// 2. 创建一个路由实例
const router = createBrowserRouter([
// 3. 传入路由配置
//...
])
从代码中可以看出,在创建路由实例的方式上 vue
和 react
都是导入对应的函数然后将路由配置传入;
需要注意的是,在 vue
中传递给 createRouter
的是一个对象,其中包含了历史模式的配置和路由配置;而在 react
中传递给 createBrowserRouter
函数只有路由配置。
路由历史模式
上面的代码中,在使用创建 vue
路由实例时除了给 createRouter
传递了一个路由配置,还额外传了一个 history
对象用于配置历史模式 (也就是我们常说的 history
模式和 hash
模式)。
下面我们来看看 vue
和 react
中是怎么区分这两种模式的:
history 模式
vue
在 vue
中通过 createWebHistory
函数来使用 history
模式:
// 1. 导入 createWebHistory 函数
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
// 2. 调用 createWebHistory 函数,使用 history 模式
history: createWebHistory(),
routes: [
//...
]
})
react
在 react
中使用 createBrowserRouter
函数创建的路由使用的就是 history
模式:
// 1. 导入 createBrowserRouter 函数
import { createBrowserRouter } from "react-router-dom";
// 2. 使用 createBrowserRouter 创建一个路由实例,历史模式为 history 模式
const router = createBrowserRouter([
//...
])
hash 模式
vue
在 vue
中使用 hash
模式,需要用到 createWebHashHistory
函数,使用方法与 history
模式相同:
// 1. 导入 createWebHashHistory 函数
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
// 2. 调用 createWebHashHistory 函数,使用 hash 模式
history: createWebHashHistory(),
routes: [
//...
]
})
react
如果需要在 react
中使用 hash
模式,则需要用到另一个函数来创建路由 —— createHashRouter
:
// 1. 导入 createHashRouter 函数
import { createHashRouter } from "react-router-dom";
// 2. 使用 createHashRouter 创建一个路由实例,历史模式为 hash 模式
const router = createHashRouter([
//...
])
总结
总结一下,就是下面这张表格:
历史模式 | 框架 | API |
---|---|---|
history 模式 | vue | createRouter + createWebHistory |
react | createBrowserRouter | |
hash 模式 | vue | createRouter + createWebHashHistory |
react | createHashRouter |
路由(route)配置
介绍了路由实例的创建与如何配置历史模式,下面我们来具体看一看 vue
和 react
的路由配置有什么异同点:
路由路径配置
vue
和 react
二者都是通过给路由对象传递一个 path
来进行路由路径的匹配:
const routes = [
{ path: '/' }
]
动态路由
vue
和 react
二者的动态路径都是以冒号 :
开头:
const routes = [
// 路由 '/order/123' 会被匹配
// 路由 '/order/456' 会被匹配
{ path: '/order/:orderId' }
]
同时,动态路径的部分会被映射到路由 params
上的相应字段中,动态路径中冒号 :
后的部分会作为 params
的 key
(如 username
),而匹配到这个动态路径的部分则会作为 key
对应的 value
(如 Jay
):
匹配模式 | 匹配路径 | params |
---|---|---|
/order/:orderId | /order/123 | { orderId: '123' } |
/users/:username/order/:orderId | /users/Jay/order/123 | { username: 'Jay', postId: '123' } |
可选路由片段
除了最基础的使用冒号 :
作为动态路由的部分,vue
和 react
还支持使用问号 ?
表示可选的路由片段:
const routes = [
// 匹配 /order 、 /order/123
{ path: '/order/:orderId?' },
]
可重复路由片段
在 vue
和 react
中还可以使用 *
号来标识这部分路由片段是可重复的:
框架 | 匹配模式 | 匹配路径 | params |
---|---|---|---|
vue | /order/:orderId* | /order | { orderId: [] } |
/order/123 | { orderId: ['123'] } | ||
/order/123/456 | { orderId: ['123', '456'] } | ||
react | /order/* | /order | { *: '' } |
/order/123 | { *: '123' } | ||
/order/123/456 | { *: '123/456' } |
这里需要特别注意的是:在 vue
中使用可重复路由片段时,在 *
号前需要用一个动态路由片段进行占位 (如上面表格中的 :orderId
),并且这个用于占位的动态路由片段最后会作为 params
中的 key
,而对应匹配的路由片段会自动根据 /
分割成数组元素作为 value
;
而在 react
中,则可以直接使用 *
;并且生成的 params
对象中的 key
就是符号 *
,而匹配的路由片段不会拆分而是作为字符串原样输出。
路由组件配置
了解了路由路径的匹配规则,我们再来看看如何给路由配置对应的组件:
vue
在 vue
中:
const routes = [
{
path: '/order',
component: { template: '<h1>Hello</h1>' }
}
]
react
在 react
中:
const routes = [
{
path: '/order',
element: <h1>Hello!</h1>
}
]
除此之外,react
还可以通过一个函数 Component
(首字母大写) 来返回路由组件:
const routes = [
{
path: '/order',
// 这里的首字母需要大写
// 函数的返回值就是对应的组件内容
Component() {
return <h1>Hello!</h1>;
}
}
]
路由组件懒加载
在 vue
和 react
中都可以通过 import
动态导入方法实现路由组件懒加载,但是具体配置有所不同:
vue
在 vue
中,我们将 component
的值替换成动态导入的方法:
const routes = [
{
path: '/order',
component: () => import(
// 组件路径
)
}
]
react
而在 react
中同样是使用 import
方法,但是需要利用一个新的配置项 lazy
来设置:
const routes = [
{
path: '/order',
lazy: () => import(
// 组件路径
)
}
]
同时,lazy
选项也可以是个函数,这样我们就能更精细的控制要加载的路由组件:
假设我们有一个 pages/Order
文件,导出了所有我们路由所需的组件:
// pages/Order 文件
// 导出组件 Main
export function Main () {
//...
}
// 导出组件 Detail
export function Detail () {
//...
}
在具体的路由配置中,就可以通过 lazy
函数来从同一个文件中加载导出的不同内容:
const routes = [
{
path: '/order',
async lazy() {
// 懒加载 Main 组件
let { Main } = await import("./pages/Order");
// 返回 Main 组件,这里的 Component 要大写
return { Component: Main };
},
children: [
{
path: ':orderId'
async lazy() {
// 懒加载 Detail 组件
let { Detail } = await import("./pages/Order");
// 返回 Detail 组件,这里的 Component 要大写
return { Component: Detail };
},
}
]
}
]
loader 选项(react 独有)
在 react
中支持给 route
配置传递一个 loader
函数,这个函数的作用是在路由对应的组件渲染之前为其提供数据:
// 导入 useLoaderData 钩子
import { useLoaderData } from 'react-router-dom';
const routes = [
{
path: '/order',
Component: () => {
// 调用 useLoaderData 钩子,在组件中使用 loader 函数传递的数据
const { data } = useLoaderData();
return (
//...
)
},
// 在 loader 中返回数据
loader: () => {
return { data: 'about' }
}
}
]
loader
函数的返回值会被传递给对应的路由组件,在组件中就可以通过 useLoaderData
这个钩子来获取对应数据,注意在使用 useLoaderData
钩子前同样需要导入。
除此之外,loader
函数还接收一个对象作为入参: { request, params }
,我们来分别看看它们的作用 ——
params
这里的 params
实际上就是我们在之前 路由路径 部分提到的路由参数:
const routes = [
{
path: '/order/:orderId',
loader: ({ params }) => {
// 输出 { orderId: xxx }
console.log(params)
}
}
]
request
另外一个参数 request
表示资源请求,其中包含了许多本次请求的信息,如 url、请求方法、body
信息等等;它等同于 Fetch Request;
const routes = [
{
path: '/order/:orderId',
loader: ({ request }) => {
const {
method, // GET
url // 如 http://xxxx/order/123
} = request;
}
}
]
action 选项(react 独有)
除了 loader
之外,react
中还支持给 route
配置传递一个 action
函数:
const routes = [
{
path: '/',
action: ({ request, params }) => {
//...
}
}
]
正常情况下,我们在 HTML 中使用表单(也就是 <form>
标签时)时,浏览器会自动序列化表单的数据,并将数据发送到服务器。
下面这段代码,当点击 提交 按钮会向服务器发送一个 post
请求:
<form method="post">
<input
type="text"
name="projectName"
defaultValue="test"
/>
<button type="submit">提交</button>
</form>
而当 action
这个函数搭配 react
的内置组件 <Form>
一起使用时,可以 阻止向服务器提交数据这一默认行为,并且可以通过 action
来获取到表单提交的数据:
// 导入 Form 组件和 useActionData 钩子
import { Form, useActionData } from 'react-router-dom';
const routes = [
{
path: '/',
Component: () => {
// 通过 useActionData 钩子获取 action 函数返回的数据
// 在这里就是 { newName: 'test' }
const data = useActionData();
// 使用内置组件 <Form> 代替默认 <form> 标签
return (
<div>
<Form method="put">
<input
type="text"
name="projectName"
defaultValue="test"
/>
<button type="submit">Update Project</button>
</Form>
</div>
)
},
action: async ({ request }) => {
// 通过 request.formData() 拿到 form 提交的表单数据
let formData = await request.formData();
// 通过 get 方法以及 input 框的 name 属性获取对应的值
let name = formData.get("projectName")
// 输出 input 框输入的值,默认为 test
console.log(name)
return { newName: name }
}
}
]
嵌套路由
在 vue
和 react
中都是通过 children
字段来实现路由的嵌套,children
的值是个数组,数组中的每一项都是一个单独的路由配置:
const routes = [
{
path: '/order',
children: [
{
//...路由配置
}
]
}
]
这里需要注意的是,嵌套的子路由的路径 path
有细微的区别 —— 在 vue
中,嵌套子路由 path
允许以绝对路径 /
开头,而在 react
中则不允许:
const routes = [
{
path: '/order',
children: [
{
// 这里的嵌套路由以绝对路径 '/' 开头
// ✅ vue 中下面路由将被视为以根路径开始疲惫
// ❌ react 会报错,只能用相对路径: 'other'
path: '/other'
}
]
}
]
路由出口
接下来我们来看看如何指定匹配到的路由组件渲染的位置,也就是路由出口的使用。
vue
vue
中通过在模板中使用内置组件 <router-view>
来指定路由出口:
const routes = [
{
path: '/order',
component: {
template: `
<h1>我是父路由</h1>
// 直接使用 router-view 进行占位
<router-view/>
`
}
}
]
react
而在 react
中,对应的内置组件则是: <Outlet />
,与 vue
不同的是,在使用 <Outlet />
之前还需要先导入它:
// 1. 从 react-router-dom 中导入 Outlet
import { Outlet } from "react-router-dom";
const routes = [
{
path: '/order',
element: (
<>
<h1>我是父路由</h1>
// 2. 使用 Outlet 进行占位
// 注意首字母大写
<Outlet />
</>
),
children: [
{
path: ':orderId',
element: <h2>我是子路由</h2>
}
]
}
]
当我们访问 /order/123
这个 url 时,我是子路由 这段文字就会消费对应的 <router-view>
与 <Outlet>
,显示在 我是父路由 文字的正下方。
路由跳转
学习了如何配置路由以及设置路由出口,下面来介绍一下如何进行路由间的跳转。
vue
在 vue
中主要有声明式和编程式两种方式来实现路由跳转:
- 声明式:使用
<router-link to="...">
; - 编程式:使用
router
实例上的方法,如:router.push
、router.replace
等等。
react
相对应的,在 react
中也提供了这两种方式:
- 声明式:使用
<Link to="...">
; - 编程式:使用
useNavigate
钩子,或者在loader
、action
中使用redirect
方法。
Link
<Link>
默认呈现的是一个可访问 <a>
元素:
// 导入 Link
import { Link } from "react-router-dom";
const routes = [
{
path: '/order',
element: (
<>
// 点击后会跳转到 /order/123
<Link to="123"> 点我跳转到 /order/123</Link>
// 点击后会跳转到 /order/123,并替换当前历史记录
// 相当于 history.replaceState()
<Link to="123" replace> 点我跳转到 /order/123</Link>
</>
)
}
]
useNavigate
基本使用方法:
import { useNavigate } from "react-router-dom";
function useLogoutTimer() {
const navigate = useNavigate();
// 将会跳转到 '/order/123' 这个 url
navigate("/order/123");
}
redirect
redirect
方法需要配合文章前面提到的 loader
和 action
来一起使用:
// 使用前同样需要先导入 redirect 方法
import { redirect } from "react-router-dom";
const routes = [
{
path: '/order/:orderId',
loader: () => {
// 将会重定向到 '/login' 这个 url
return redirect("/login")
},
action: () => {
// 将会重定向到 '/order' 这个 url
return redirect("/order")
}
}
]
结束语
以上就是 Vue Router
与 React Router
的一个简单比较啦~
当然这里只介绍了 React Router
的基础内容,还有很多 API
、hooks
等在文章里没有提及;如果各位小伙伴想深入学习的话可以查阅官网: React Router (还可以锻炼英文水平🤭)