likes
comments
collection
share

Vue Router 玩家快速入门 React Router

作者站长头像
站长
· 阅读数 23

之前在工作中一直使用的是 Vue Router,不久前接触到了 React Routerv6 版本;在学习过程中,发现两者还是有不少相似的地方。

这里将这些异同点简单做个比较与汇总,希望能通过这种类比的方式,帮助和我一样的新手快速掌握 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. 传入路由配置
  //...
])

从代码中可以看出,在创建路由实例的方式上 vuereact 都是导入对应的函数然后将路由配置传入;

需要注意的是,vue 中传递给 createRouter 的是一个对象,其中包含了历史模式的配置和路由配置;而在 react 中传递给 createBrowserRouter 函数只有路由配置

路由历史模式

上面的代码中,在使用创建 vue 路由实例时除了给 createRouter 传递了一个路由配置,还额外传了一个 history 对象用于配置历史模式 (也就是我们常说的 history 模式和 hash 模式)

下面我们来看看 vuereact 中是怎么区分这两种模式的:

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)配置

介绍了路由实例的创建与如何配置历史模式,下面我们来具体看一看 vuereact 的路由配置有什么异同点:

路由路径配置

vuereact 二者都是通过给路由对象传递一个 path 来进行路由路径的匹配:

const routes = [
  { path: '/' }
]

动态路由

vuereact 二者的动态路径都是以冒号 : 开头:

const routes = [
  // 路由 '/order/123' 会被匹配
  // 路由 '/order/456' 会被匹配
  { path: '/order/:orderId' }
]

同时,动态路径的部分会被映射到路由 params 上的相应字段中,动态路径中冒号 : 后的部分会作为 paramskey(如 username),而匹配到这个动态路径的部分则会作为 key 对应的 value(如 Jay):

匹配模式匹配路径params
/order/:orderId/order/123{ orderId: '123' }
/users/:username/order/:orderId/users/Jay/order/123{ username: 'Jay', postId: '123' }

可选路由片段

除了最基础的使用冒号 : 作为动态路由的部分,vuereact 还支持使用问号 ? 表示可选的路由片段:

const routes = [
  // 匹配 /order 、 /order/123
  { path: '/order/:orderId?' },
]

可重复路由片段

vuereact 中还可以使用 * 号来标识这部分路由片段是可重复的:

框架 匹配模式 匹配路径 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>;
    }
  }
]

路由组件懒加载

vuereact 中都可以通过 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 }
    }
  }
]

嵌套路由

vuereact 中都是通过 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 中主要有声明式和编程式两种方式来实现路由跳转:

  1. 声明式:使用 <router-link to="...">
  2. 编程式:使用 router 实例上的方法,如:router.pushrouter.replace 等等。

react

相对应的,在 react 中也提供了这两种方式:

  1. 声明式:使用 <Link to="...">
  2. 编程式:使用 useNavigate 钩子,或者在 loaderaction 中使用 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 方法需要配合文章前面提到的 loaderaction 来一起使用:

// 使用前同样需要先导入 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 RouterReact Router 的一个简单比较啦~

当然这里只介绍了 React Router 的基础内容,还有很多 APIhooks 等在文章里没有提及;如果各位小伙伴想深入学习的话可以查阅官网: React Router (还可以锻炼英文水平🤭)