Vue-Router 官网速通(下)

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Vue-Router 官网速通(下)

Vue-Router 官网速通(上)https://developer.aliyun.com/article/1513239?spm=a2c6h.13148508.setting.27.f8774f0euyBLtl

三. 进阶

1. 导航守卫

导航守卫通过跳转或取消的方式守卫导航。路由导航包括:全局,单个路由,组件级。

1.1 全局前置守卫

使用 router.beforeEach 注册全局前置守卫,导航跳转前触发,接受两个参数,to 即将进入的路由,from 即将离开的路由,返回 false 是取消当前跳转,返回一个路由是跳转到返回路由中:

const router = createRouter({ ... })
 
router.beforeEach(async (to, from) => {
  if (
    // 检查用户是否已登录
    !isAuthenticated &&
    // ❗️ 避免无限重定向
    to.name !== "Login"
  ) {
    // 将用户重定向到登录页面
    return { name: "Login" };
  }
});

1.2 全局解析守卫

使用 router.beforeResolve 注册全局解析守卫。解析守卫在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。是获取数据或执行任何其他操作(如用户无法进入页面时希望避免执行的操作)的理想位置。例如:确保用户可以访问自定义 meta 属性 requiresCamera 的路由:

router.beforeResolve(async (to) => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission();
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false;
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error;
      }
    }
  }
});

1.3 全局后置钩子

使用 router.afterEach 注册全局后置钩子,后置钩子不会改变导航本身,可以用于分析、更改页面标题、声明页面等辅助功能以及许多其他事情:

router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath);
});

1.4 在守卫内的全局注入

使用 inject() 方法在导航守卫内注入全局属性。app.provide() 中所有内容都可以在 router.beforeEach()、router.beforeResolve()、router.afterEach() 内获取到:

// main.ts
const app = createApp(App);
app.provide("global", "hello injections");
 
// router.ts or main.ts
router.beforeEach((to, from) => {
  const global = inject("global"); // 'hello injections'
  // a pinia store
  const userStore = useAuthStore();
  // ...
});

1.5 路由独享的守卫

在路由配置上定义 beforeEnter 守卫,在进入路由时触发,不会在 params、query、hash 改变时触发:

const routes = [
  {
    path: "/users/:id",
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false;
    },
  },
];

也可以将函数数组传给 beforeEnter,这在为不同的路由重用守卫时很有用:

function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash };
}
 
function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: "" };
}
 
const routes = [
  {
    path: "/users/:id",
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: "/about",
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
];

1.6 组件内的守卫

在路由组件内直接定义路由导航守卫,onBeforeRouteLeave 离开组件时调用,onBeforeRouteUpdate 路由更新时调用

import { onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";
 
onBeforeRouteLeave((to, from) => {});
onBeforeRouteUpdate((to, from) => {});

1.7 完整的导航解析流程

导航被触发。
在失活的组件里调用 onBeforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 onBeforeRouteUpdate 守卫(2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
调用全局的 beforeResolve 守卫(2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入

2. 路由元信息

使用 meta 定义路由元信息,可以存放任何信息,如过渡名,路由访问权限等,可以在导航守卫被访问到:

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: { requiresAuth: true },
      },
      {
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: { requiresAuth: false },
      }
    ]
  }
]

怎么访问 meta 字段呢?

router.beforeEach((to, from) => {
  // 而不是去检查每条路由记录
  // to.matched.some(record => record.meta.requiresAuth)
  if (to.meta.requiresAuth && !auth.isLoggedIn()) {
    // 此路由需要授权,请检查是否已登录
    // 如果没有,则重定向到登录页面
    return {
      path: "/login",
      // 保存我们所在的位置,以便以后再来
      query: { redirect: to.fullPath },
    };
  }
});

2.1 TypeScript

可以继承来自 vue-router 中的 RouteMeta 来为 meta 字段添加类型:

// 添加到一个 `.d.ts` 文件中。将这个文件包含在项目的 `tsconfig.json` 中的 "file" 字段内。
import "vue-router";
 
// 为了确保这个文件被当作一个模块,添加至少一个 `export` 声明
export {};
 
declare module "vue-router" {
  interface RouteMeta {
    // 是可选的
    isAdmin?: boolean;
    // 每个路由都必须声明
    requiresAuth: boolean;
  }
}

3. 数据获取

获取数据可以导航后在组件生命周期获取,也可以在导航前,在路由守卫获取。

3.1 导航完成后获取数据

马上导航和渲染组件,在 created 钩子中获取数据。

<script setup lang="ts">
  import { useRoute } from "vue-router";
  import { watchEffect, reactive } from "vue";
 
  const route = useRoute();
 
  watchEffect(() => {
    fetchData(route.params.id);
  });
 
  const post = reactive(null);
  const fetchData = (id) => {
    getPost(id, (err, post) => {
      post = post;
    });
  };
</script>
 
<template>
  <div class="post">
    <div class="content">
      <h2>{{ post.title }}</h2>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>

4. 组合式 API

4.1 在 setup 中访问路由和当前路由

不能直接访问 this.routerthis.router 或 this.route。需使用 useRouter 和 useRoute 函数,但在模板中可以访问 routerrouter 和 route:

<script setup lang="ts">
  import { useRoute, useRouter } from "vue-router";
 
  const route = useRoute();
  const router = useRouter();
 
  const handleGo = () => {
    router.push({ name: "Home", query: { ...route.query } });
  };
</script>
 
<template>
  <div class="demo" @click="handleGo">{{ $route.params }}</div>
</template>

4.2 导航守卫

onBeforeRouteLeave,onBeforeRouteUpdate 离开和更新守卫:

<script setup lang="ts">
  import {
    useRouter,
    onBeforeRouteLeave,
    onBeforeRouteUpdate,
  } from "vue-router";
 
  const router = useRouter();
 
  onBeforeRouteUpdate(() => {
    console.log("更新路由");
  });
 
  onBeforeRouteLeave(() => {
    console.log("离开路由");
  });
 
  const changeRoute = () => {
    router.push("/demo/2");
  };
</script>
 
<template>
  <div class="demo" @click="changeRoute">改变路由</div>
</template>

4.3 useLink

利用 RouterLink 构建自己的 RouterLink 组件或生成自定义链接:

<script setup lang="ts">
  import { RouterLink } from "vue-router";
  import { computed } from "vue";
 
  const props = defineProps({
    // @ts-ignore
    ...RouterLink.props,
    inactiveClass: String,
    _target: {
      type: String,
      default: "_self",
    },
  });
 
  const isExternalLink = computed(
    () => typeof props.to === "string" && props.to.startsWith("http")
  );
</script>
 
<template>
  <a v-if="isExternalLink" :href="to" :target="_target"><slot /></a>
  <router-link v-else v-bind="$props" :to="$props.to"><slot /></router-link>
</template>

5. RouterView 插槽

RotuerView 暴露插槽,用来渲染路由组件:

<router-view v-slot="{ Component }">
  <component :is="Component" />
</router-view>
 
<!-- 等价于 -->
<router-view />

5.1 KeepAlive & Transition

KeepAlive 是保持路由组件活跃,而不是 RouterView 本身。所以需要将 KeepAlive 放置在插槽内:

<router-view v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" />
  </keep-alive>
</router-view>

同理 Transition 实现路由组件之间切换的过渡效果:

<router-view v-slot="{ Component }">
  <transition>
    <component :is="Component" />
  </transition>
</router-view>

5.2 模板引用

可以将模板引用放置在路由组件上:

<router-view v-slot="{ Component }">
  <component :is="Component" ref="mainContent" />
</router-view>

6 过渡动效

要实现路由组件切换需要使用  插槽:

<router-view v-slot="{ Component }">
  <transition name="fade">
    <component :is="Component" />
  </transition>
</router-view>

6.1 单个路由的过渡

使用元信息和动态 name 结合实现每个路由的不同过渡效果:

const routes = [
  {
    path: "/custom-transition",
    component: PanelLeft,
    meta: { transition: "slide-left" },
  },
  {
    path: "/other-transition",
    component: PanelRight,
    meta: { transition: "slide-right" },
  },
];
<router-view v-slot="{ Component, route }">
  <!-- 默认过渡效果 `fade` -->
  <transition :name="route.meta.transition || 'fade'">
    <component :is="Component" />
  </transition>
</router-view>

6.2 复用视图进行过渡

复用路由组件会忽略过渡,可以添加 key 属性来强制过渡:

<router-view v-slot="{ Component, route }">
  <transition name="fade">
    <component :is="Component" :key="route.path" />
  </transition>
</router-view>

7. 滚动行为

通过 scrollBehavior 实现滚动效果,接收 to 和 from 路由对象:

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

通过 el 传递 CSS 选择器或 DOM 元素。top 和 left 的偏移量相对于该元素。

const router = createRouter({
  scrollBehavior(to, from) {
    // 始终在元素 #main 上方滚动 10px
    return {
      // 也可以这么写
      // el: document.getElementById('main'),
      el: "#main",
      // 在元素上 10 像素
      top: 10,
    };
  },
});

模拟 “滚动到锚点” 的行为:

const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    if (to.hash) {
      return {
        el: to.hash,
      };
    }
  },
});

如果浏览器支持滚动行为,设置 behavior 让它变得更流畅:

const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    if (to.hash) {
      return {
        el: to.hash,
        behavior: "smooth",
      };
    }
  },
});

7.1 延迟滚动

通过返回一个 Promise,实现延时滚动:

const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ left: 0, top: 0 });
      }, 500);
    });
  },
});

8. 路由懒加载

当打包时,JS 包会非常大,影响用户体验,使用动态导入代替静态导入:

// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')
 
const router = createRouter({
  // ...
  routes: [
    { path: '/users/:id', component: UserDetails }
    // 或在路由定义里直接使用它
    { path: '/users/:id', component: () => import('./views/UserDetails.vue') },
  ],
})

8.1 把组件按组分块

8.1.1 使用 webpack

把某个路由下的所有组件打包到同个异步块 (chunk) 中。使用命名 chunk,特殊的注释语法:

const UserDetails = () =>
  import(/* webpackChunkName: "group-user" */ "./UserDetails.vue");
const UserDashboard = () =>
  import(/* webpackChunkName: "group-user" */ "./UserDashboard.vue");
const UserProfileEdit = () =>
  import(/* webpackChunkName: "group-user" */ "./UserProfileEdit.vue");
8.1.2 使用 Vite

使用 rollupOptions 下定义分块:

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      // https://rollupjs.org/guide/en/#outputmanualchunks
      output: {
        manualChunks: {
          "group-user": [
            "./src/UserDetails",
            "./src/UserDashboard",
            "./src/UserProfileEdit",
          ],
        },
      },
    },
  },
});

9. 扩展 RouterLink

自定义 RouterLink 实现导航菜单链接,处理外部链接:

<script setup lang="ts">
  import { RouterLink } from "vue-router";
  import { computed } from "vue";
 
  const props = defineProps({
    // @ts-ignore
    ...RouterLink.props,
    _target: {
      type: String,
      default: "_self",
    },
  });
 
  const isExternalLink = computed(
    () => typeof props.to === "string" && props.to.startsWith("http")
  );
</script>
 
<template>
  <a v-if="isExternalLink" v-bind="$attrs" :href="to" :target="_target"
    ><slot
  /></a>
  <router-link v-else v-bind="$props" :to="$props.to"><slot /></router-link>
</template>

10. 导航故障

10.1 检测导航故障

如果导航被阻,可以通过 router.push 返回的 Promise 的解判断是否离开了当前位置:

const navigationResult = await router.push("/my-profile");
 
if (navigationResult) {
  // 导航被阻止
} else {
  // 导航成功 (包括重新导航的情况)
  this.isMenuOpen = false;
}

10.2 全局导航故障

使用 router.afterEach() 检测全局导航故障:

router.afterEach((to, from, failure) => {
  if (failure) {
    sendToAnalytics(to, from, failure);
  }
});

11. 动态路由

11.1 添加/移除 路由

使用 router.addRoute() 和 router.removeRoute() 实现动态路由的添加和删除。

router.addRoute({ path: "/about", component: About });
router.removeRoute("About"); // 使用 name 移除

11.2 在导航守卫中添加路由

通过返回新的位置来触发重定向:

router.beforeEach((to) => {
  if (!hasNecessaryRoute(to)) {
    router.addRoute(generateRoute(to));
    // 触发重定向
    return to.fullPath;
  }
});

11.3 添加嵌套路由

将路由的 name 作为第一个参数传递给 router.addRoute():

router.addRoute({ name: "admin", path: "/admin", component: Admin });
router.addRoute("admin", { path: "settings", component: AdminSettings });
// 等价于
router.addRoute({
  name: "admin",
  path: "/admin",
  component: Admin,
  children: [{ path: "settings", component: AdminSettings }],
});

11.4 查看现有路由

Vue Router 提供了两个功能来查看现有的路由:

| router.hasRoute(routerName):检查路由是否存在。

| router.getRoutes():获取一个包含所有路由记录的数组。


目录
相关文章
|
17天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的福泰轴承股份有限公司进销存系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的福泰轴承股份有限公司进销存系统附带文章源码部署视频讲解等
13 0
|
17天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的七彩云南文化旅游网站附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的七彩云南文化旅游网站附带文章源码部署视频讲解等
38 16
|
1月前
|
移动开发 JavaScript 前端开发
Vue-Router 官网速通(上)
Vue-Router 官网速通(上)
16 1
|
1月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的桂林旅游景点导游平台附带文章和源代码
基于SpringBoot+Vue的桂林旅游景点导游平台附带文章和源代码
23 1
|
1月前
|
前端开发
SASS 官方文档速通
SASS 官方文档速通
22 1
|
18天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的桂林旅游景点导游平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的桂林旅游景点导游平台附带文章源码部署视频讲解等
10 0
|
18天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的乡村研学旅行平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的乡村研学旅行平台附带文章源码部署视频讲解等
10 0
|
1月前
|
存储 JavaScript API
Pinia 官网速通
Pinia 官网速通
11 0
|
1月前
|
JSON JavaScript 前端开发
axios 官网速通
axios 官网速通
16 0
|
1月前
|
JSON 前端开发 JavaScript
Vite 官方文档速通(上)
Vite 官方文档速通(上)
52 0