vue-router 4 版本学习笔记

7,095 阅读8分钟

vue-router 4 的用法

vue-router4保持了大部分API不变

yarn add vue-router@4

src目录下新建router/index.js

import { createRouter, createWebHashHistory} from 'vue-router'


const router = createRouter({
  history: createWebHashHistory(),
  routes:[
    {
      path:"/",
      name: "home",
      component: () => import('../views/home.vue')
    }
  ],
})

export default router

在main.js中引入router,并引用

import { createApp } from 'vue'
import App from './App.vue'

import router from './router'

createApp(App)
  .use(router)
  .mount('#app')

动态路由的添加

addRouter() 作用是权限控制时动态的追加一些路由

  • 当接收两个参数时,第一个参数为父路由name的字符串,第二个为要添加的路由记录的对象

    添加一条新的路由记录作为现有路由的子路由

  • 当接受一个参数时,参数仅为要添加的路由记录对象

    添加一条新的路由记录到路由。

如果路由有一个 name,并且已经有一个与之名字相同的路由,它会先删除之前的路由。

router.addRouter({
  path: '/about',
  name: 'about',
  component: () => import('./component/About.vue')
})

router.addRouter('about',{
  path: 'about/info',
  component: () => import('./composition/about/info.vue')
})

composition API

导航守卫

添加一个导航守卫,在当前位置的组件状态变更时触发。可以在任何组件中使用。当组件被卸载时,导航守卫将被移除。

  • onBeforeRouteLeave() —— 在当前位置的组件将要离开时触发

    <script setup>
      // 离开当前页面时弹出对话框,点击否,返回false,页面不会跳转
      onBeforeRouteLeave((to, from) => {
        const answer = window.confirm('你确定离开当前页面吗?')
        if(!answer) {
          return false;
        }
      })
    </script>
    
  • onBeforeRouteUpdate() —— 在当前位置即将更新时触发

    使用方法同onBeforeRouteLeave()

  • afterEach() —— 在每次导航后执行

    router.afterEach((to, from, failure) => {
      if (isNavigationFailure(failure)) {
        console.log('failed navigation', failure)
      }
    })
    
  • beforeEach() —— 在任何导航前执行

  • beforeResolve() —— 在导航即将解析之前执行,这个状态下,所有的组件都已经被获取,并且其他导航守卫也已经成功

    router.beforeResolve(to => {
      if (to.meta.requiresAuth && !isAuthenticated) return false
    })
    

路由进退

  • router.getRoutes() —— 获取所有路由记录的完整列表

  • back() —— 如果可能的话,通过调用 history.back() 回溯历史。相当于 router.go(-1)

  • forward() —— 如果可能的话,通过调用 history.forward() 在历史中前进。相当于 router.go(1)

  • router.go( Number ) —— 允许你在历史中前进或后退

  • router.push( path )—— 通过在历史堆栈中推送一个 entry,以编程方式导航到一个新的 URL

编程式导航

由于vue3没有了 this,无法通过this.$router获取路由实例,当需要实现连接跳转的同时执行一些操作需要获取到路由实例和route对象,则需要用到下面的方法

  • useRouter() —— 返回 router 实例
  • useRoute() —— 返回当前路由地址

相当于在模板中使用 $route。必须在 setup() 中调用

<template>
<!-- ... -->
<button @click='backToHome'>返回首页</button>
</template>
<script setup>
  import { useRouter, useRoute } from 'vue-router'

  //通过useRouter()获取路由器的实例
  const router = useRouter()
  backToHome(){
    router.push('/')
  }

  //route是响应式对象,可监控其变化,需要用useRoute()获取
  const route = useRoute()
  watch(() => route.query, query => {
    console.log(query)
  })
</script>

useLink()

返回 v-slot API 暴露的所有内容

vue-router 4 的变化

history 替代了mode

这样可以更好的创建路由实例,并自定义这个实例

const router = createRouter({
  history: createWebHashHistory(), // ← ← ← ← ← ← ← ←
  routes: [
    {
      path: "/",
      name: "home",
      component: () => import('./components/Home.Vue')
    }
  ]
})

createWebHistory(base)

创建一个 HTML5 历史,即单页面应用程序中最常见的历史记录

base为可选参数,当应用程序被托管在非站点根目录文件夹中,诸如 https://example.com/folder/ 之类时非常有用

createWebHistory() // 没有 base,应用托管在域名 https://example.com 的根目录下。
createWebHistory('/folder/') // 给出的网址为 https://example.com/folder/

createWebHashHistory(base)

创建一个 hash 历史记录。

对于没有主机的 web 应用程序 (例如 file://),或当配置服务器不能处理任意 URL 时这非常有用

注意:如果 SEO 对你很重要,你应该使用 createWebHistory

提供一个可选的base,默认是 location.pathname + location.search。如果 head 中有一个 <base>,它的值将被忽略,而采用这个参数。但请注意它会影响所有的 history.pushState() 调用,这意味着如果你使用一个 <base> 标签,它的 href必须与这个参数相匹配 (请忽略 # 后面的所有内容)

// at https://example.com/folder
createWebHashHistory() // 给出的网址为 https://example.com/folder#
createWebHashHistory('/folder/') // 给出的网址为 https://example.com/folder/#

// 如果在 base 中提供了 `#`,则它不会被 createWebHashHistory 添加
createWebHashHistory('/folder/#/app/') // 给出的网址为 https://example.com/folder/#/app/

// 你应该避免这样做,因为它会更改原始 url 并打断正在复制的 url
createWebHashHistory('/other-folder/') // 给出的网址为 https://example.com/other-folder/#

// at file:///usr/etc/folder/index.html
// 对于没有 `host` 的位置,base被忽略
createWebHashHistory('/iAmIgnored') // 给出的网址为 file:///usr/etc/folder/index.html#

createMemoryHistory(base)

创建一个基于内存的历史记录。这个历史记录的主要目的是处理 SSR。它在一个特殊的位置开始,这个位置无处不在。如果用户不在浏览器上下文中,它们可以通过调用 router.push()router.replace() 将该位置替换为启动位置。

Base 适用于所有 URL,默认为'/'

通配符*被移除

现在必须使用自定义的 regex 参数来定义所有路由(*/*):

const routes = [
  // pathMatch 是参数的名称,例如,跳转到 /not/found 会得到
  // { params: { params: { pathMatch: ['not', 'found'] }}
  // 这要归功于最后一个 *,意思是重复的参数,如果你
  // 打算直接使用未匹配的路径名称导航到该路径,这是必要的
  { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
  // 如果你省略了最后的 `*`,在解析或跳转时,参数中的 `/` 字符将被编码
  { path: '/:pathMatch(.*)', name: 'bad-not-found', component: NotFound },
]
// 如果使用命名路由,不好的例子:
router.resolve({
  name: 'bad-not-found',
  params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
// 好的例子:
router.resolve({
  name: 'not-found',
  params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'

isReady() 替代 onReady()

这个方法用于底层的服务端渲染

当路由器完成初始化导航时,返回一个 Promise,这意味着它已经解析了所有与初始路由相关的异步输入钩子和异步组件。如果初始导航已经发生了,那么 promise 就会立即解析。这在服务器端渲染中很有用,可以确保服务器和客户端的输出一致。需要注意的是,在服务器端,你需要手动推送初始位置,而在客户端,路由器会自动从 URL 中获取初始位置。当路由器完成初始化导航时,返回一个 Promise,这意味着它已经解析了所有与初始路由相关的异步输入钩子和异步组件。如果初始导航已经发生了,那么 promise 就会立即解析。这在服务器端渲染中很有用,可以确保服务器和客户端的输出一致。需要注意的是,在服务器端,你需要手动推送初始位置,而在客户端,路由器会自动从 URL 中获取初始位置。

// 将
router.onReady(onSuccess, onError)
// 替换成
router.isReady().then(onSuccess).catch(onError)
// 或者使用 await:
try {
  await router.isReady()
  // 成功
} catch (err) {
  // 报错
}

scrollBehavior

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。

当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:

const router = createRouter({
  history: createWebHashHistory(),
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
  }
})

scrollBehavior 函数接收 to from 路由对象,如 Navigation Guards。第三个参数 savedPosition,只有当这是一个 popstate 导航时才可用(由浏览器的后退/前进按钮触发)。

例子:

返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样滚到之前的位置,而不是跳到页面顶部

const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { top: 0 }
    }
  },
})

<router-view><v-slot>

现在 keep-alive 和 transition 必须用在 router-view 内部

<router-view> 暴露了一个 v-slot API,主要使用 <transition><keep-alive> 组件来包裹你的路由组件

<!-- 以前的写法  -->
<keep-alive>
  <transition>
    <router-view></router-view>
  </transition>
</keep-alive>

<!-- 现在的写法 -->
<!-- 通过v-slot获取内部的组件和路由 -->
<router-view v-slot="{ Component, route }">
  <!-- 路由的meta内属性绑定过度行为 -->
  <transition :name="route.meta.transition || 'fade'" mode="out-in">
    <keep-alive>
      <!-- 通过:is动态渲染组件到keep-alive内部 -->
      <component :is="Component" :key="route.meta.usePathKey ? route.path : undefined"/>
    </keep-alive>
  </transition>
</router-view>

router-link移除的属性

append

移除append —— 可以手动将值设置到现有的 path

<router-link to="child-route" append>to relative child</router-link>
替换成
<router-link :to="append($route.path, 'child-route')">
  to relative child
</router-link>

你必须在 App 实例上定义一个全局的 append 函数:

app.config.globalProperties.append = (path, pathToAppend) => {
 	return path + (path.endsWith('/') ? '' : '/') + pathToAppend 
}

tag/event

tag属性是定义router-link渲染结果的标签,由于不再是a标签,点击没了跳转,所以需要事件触发

现在<router-link> 中的 eventtag 属性都已被删除。可以使用 v-slot API 来完全定制 <router-link>

<router-link to="/about" tag="span" event="dblclick">About Us</router-link>
替换成
<router-link to="/about" custom v-slot="{ navigate }">
  <span @click="navigate" @keypress.enter="navigate" role="link">About Us</span>
</router-link>

exact

现在完全匹配逻辑简化,特殊的业务逻辑由用户自己解决,如想自定义这种行为,例如考虑到 hash 部分,应该使用 v-slot API 来扩展<router-link>

mixins 中的路由守卫将被忽略

resolve 替代 match

router.matchrouter.resolve 已合并到 router.resolve 中,签名略有不同

原因:将用于同一目的的多种方法统一起来。

router.resolve()返回路由地址标准化版本。还包括一个包含任何现有 basehref 属性。

移除router.getMatchedComponents()

这个方法现在被删除了,因为匹配的组件可以从 router.currentRoute.value.mixed 中获取:

router.currentRoute.value.matched.flatMap(record =>
  Object.values(record.components)
)

原因:这个方法只在 SSR 中使用,并且是用户一行就能完成的操作。

包括首屏导航在内的所有导航均为异步

所有的导航,包括第一个导航,现在都是异步的,这意味着,如果使用一个 transition,可能需要等待路由 ready 好后再挂载程序:

app.use(router)
router.isReady().then(() => app.mount('#app'))

如果首屏存在路由守卫,则可以不等待就绪直接挂载,产生结果将和 vue2 相同

移除 route 的 parent 属性

parent 属性已从标准化路由地址(this.$routerouter.resolve 返回的对象)中删除。你仍然可以通过 matched 数组访问它:

const parent = this.$route.matched[this.$route.matched/length - 2]

原因:同时存在 parentchildren 会造成不必要的循环引用,而属性可以通过 matched 来检索

history.state 的用法

Vue Router 将信息保存在 history.state 上。如果你有任何手动调用 history.pushState() 的代码,你应该避免它,或者用的 router.push()history.replaceState() 进行重构:

// 将
history.pushState(myState, '', url)
// 替换成
await router.push(url)
history.replaceState({ ...history.state, ...myState }, '')

同样,如果你在调用 history.replaceState() 时没有保留当前状态,你需要传递当前 history.state

// 将
history.replaceState({}, '', url)
// 替换成
history.replaceState(history.state, '', url)

options 中需要配置 routes

原因:路由的设计是为了创建路由,尽管你可以在以后添加它们。在大多数情况下,你至少需要一条路由,一般每个应用都会编写一次。

createRouter({ routes: [] })

一些错误处理的变更

跳转不存在的命名路由会报错

router.push({ name: homee})

原因:以前,路由会导航到 /,但不显示任何内容(而不是主页)。抛出一个错误更有意义,因为我们不能生成一个有效的 URL 进行导航

缺少必填参数会抛出异常

在没有传递所需参数的情况下跳转或解析命名路由,会产生错误:

// 给与以下路由:
const routes = [{ path: '/users/:id', name: 'user', component: UserDetails }]

// 缺少 `id` 参数会失败
router.push({ name: 'user' })
router.resolve({ name: 'user' })

命名子路由如果 path 为空的时候不在追加 /

给予任何空 path 的嵌套命名路由:

const routes = [
  {
    path: '/dashboard',
    name: 'dashboard-parent',
    component: DashboardParent
    children: [
      { path: '', name: 'dashboard', component: DashboardDefault },
      { path: 'settings', name: 'dashboard-settings', component: DashboardSettings },
    ],
  },
]

现在,导航或解析到命名的路由 dashboard 时,会产生一个不带斜线的 URL

router.resolve({ name: 'dashboard' }).href // '/dashboard'

这对子级 redirect 有重要的副作用,如下所示:

const routes = [
  {
    path: '/parent',
    component: Parent,
    children: [
      // 现在将重定向到 `/home` 而不是 `/parent/home`
      { path: '', redirect: 'home' },
      { path: 'home', component: Home },
    ],
  },
]

请注意,如果 path/parent/,这也可以,因为 home/parent/ 的相对地址确实是 /parent/home,但 home/parent 的相对地址是 /home

原因:这是为了使尾部的斜线行为保持一致:默认情况下,所有路由都允许使用尾部的斜线。可以通过使用 strict 配置和手动添加(或不添加)斜线来禁用它。

$route 属性编码

无论在哪里启动导航,paramsqueryhash 中的解码值现在都是一致的(旧的浏览器仍然会产生未编码的 pathfullPath)。初始导航应产生与应用内部导航相同的结果。

  • Path/fullpath 不再做解码
  • hash 会被解码
  • push、resolve 和 replace,字符串参数,或者对象参数path属性必须编码
  • params / 会被解码
  • query中 + 不处理,之前会处理为 %2b,如需处理可以使用 stringifyQuery()