Vue-Router 官网速通(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 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():获取一个包含所有路由记录的数组。


目录
相关文章
|
3月前
|
JavaScript API
再不学vue3就没有人要你了!速来围观vue3
这篇技术文章介绍了作者从最初对学习 Vue3 抵触到最终决定投入学习的心路历程,展示了 Vue3 相较于 Vue2 的诸多改进和新特性,如更优的性能、更小的代码体积及更佳的 TypeScript 支持。文章重点阐述了 Vue3 中 createApp 的使用变化、emits 机制、多事件处理、Fragment 的引入等重要功能升级。此外,还深入探讨了 Composition API 和 Options API 的区别,强调 Composition API 在代码组织和逻辑复用方面的优势,并给出了在不同项目规模中选择合适 API 的建议。
46 0
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的高考志愿咨询平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的高考志愿咨询平台附带文章源码部署视频讲解等
41 1
|
7月前
|
移动开发 JavaScript 前端开发
Vue-Router 官网速通(上)
Vue-Router 官网速通(上)
31 1
|
7月前
|
前端开发
SASS 官方文档速通
SASS 官方文档速通
41 1
|
7月前
|
存储 JavaScript API
Pinia 官网速通
Pinia 官网速通
46 0
|
7月前
|
JSON JavaScript 前端开发
axios 官网速通
axios 官网速通
52 0
|
7月前
|
存储 JSON 缓存
Vite 官方文档速通(下)
Vite 官方文档速通(下)
117 0
|
7月前
|
JSON 前端开发 JavaScript
Vite 官方文档速通(上)
Vite 官方文档速通(上)
100 0
|
7月前
|
JavaScript API
Vue3 官方文档速通(中)
Vue3 官方文档速通(中)
308 0
|
7月前
|
JavaScript 前端开发 安全
Vue3官方文档速通(下)
Vue3官方文档速通(下)
108 0