0


Vue Router 4 的使用,一篇文章给你讲透彻

Vue 3.X 使用Vue Router 4.x 进行路由配置,本文我们就来研究下如何使用Vue Router 4.x,本文中所有的使用方式都是使用 Composition API的方式。

本文通过一步步介绍Vue Router 4.x的使用,来搭建一个简单的博客系统,让你对新版的Vue Router 4.x有一个完整的认识,然后能够非常轻松滴将Vue Router 4.x应用在自己的项目中。

项目初始化

项目搭建

项目使用

vite

进行创建。

npm init vite@latest vue-router-blog
npm install
npm run dev

目前安装的是

Vue 3.2.25
配置
vite.config.js

我们配置

@

别名,这样就比较方便书写引入文件的路径

// 引入文件
const path = require("path");

export default defineConfig({
  // 添加 @
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  plugins: [vue()],
});
配置
jsconfig.json
jsconfig.json

可以让

VSCode

更加智能

{
  "include": [
    "./src/**/*",
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Vue Router 4

初体验

安装
Vue Router 4
npm i vue-router@4

目前安装的是

Vue Router 4.0.12
创建两个页面
Home.vue

About.vue
<!-- Home.vue -->
<template>
  <div>
    主页
  </div>
</template>
<!-- About.vue -->
<template>
  <div>
    关于页
  </div>
</template>

这两个页面很简单,每个页面仅仅就是显示一行文字

创建
router

我们在src目录下新建router目录,在router目录下创建index.js文件, 在里面进行路由的信息配置。

import { createRouter, createWebHistory } from "vue-router";

// 引入
import Home from "@/views/Home.vue";
import About from "@/views/About.vue";

// 路由信息
let routes = [
  {
    path: "/",
    name: 'home',
    component: Home,
  },
  {
    path: "/about",
    name: 'about',
    component: About,
  },
];

// 路由器
const router = createRouter({
  history: createWebHistory(), // HTML5模式
  routes,
});

export default router;
安装
router

将路由安装

router

安装到app上。

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

// 引入插件
import router from "@/store/index";
// 安装router插件
createApp(App).use(router).mount('#app')
使用
router-link

router-view

修改

App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" /><br />
  <div>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link><br />
  </div>
  <router-view></router-view>
</template>

至此,我们的就实现了页面间的切换功能了。

效果

几个重要的概念

router-link

组件和

a

标签的区别?

router-link

组件底层也是渲染的

a

标签,但是

router-link

的页面切换只是更新了页面的部分内容,不会进行整个页面的刷新,而

a

标签跳转(例如:

<a href="/about">调到Home标签</a><br>

)是对整个页面进行刷新。

底层原理是

router-link

劫持了元素的点击事件,添加了处理页面更新的逻辑。

Hash

模式和

HTML5

模式的区别?

Hash

模式的URL中有一个

#

号,

eg:http://localhost:3000/#/about

,

#

号后面的就是Hash地址,这个模式以前是SPA的常用模式,但是链接有一个

#

号比较丑。

HTML5

模式和正常的链接地址一样的,

eg:http://localhost:3000/about

, 这个地址很优雅,但是有一个问题,需要服务器支持。 原因是浏览器中输入

http://localhost:3000/about

支持,服务器以为要访问根路劲下的

about

目录的HTML文件,而不是访问根路劲下的HTML文件。

webpackvite启动的服务器是支持

HTML5

模式的,所以开发环境使用

HTML5

模式没有问题。

router-link

组件和

router-view

组件为什么能直接使用?

安装

router

插件的时候注册了这两个全局组件,所以能直接使用。

install(app: App) {
    app.component('RouterLink', RouterLink)
    app.component('RouterView', RouterView)
}

路由懒加载

上面写法有一个严重的问题,

router

中所有的组件都会被一次加载。我们的例子中就是

Home

About

组件,即使有时候不会用到

About

组件, 也要加载,这对首页的显示会有很大的影响。

改造如下:

<!--// 删除 import Home from "@/views/Home.vue";-->
<!--// 删除 import About from "@/views/About.vue";-->

let routes = [
  {
    path: "/",
    name: 'home',
    <!--// 改成如下的写法-->
    component: () => import("@/views/Home.vue"),
  },
  {
    path: "/about",
    name: 'about',
    <!--// 改成如下的写法-->
    component: () => import("@/views/About.vue"),
  },
];

这样在开发环境中只有使用到组件才会加载进来,在生产环境中异步组件会分开文件进行打包。

修改代码(创建博客的框架)

为了方便介绍其他内容,我们修改一下代码内容:

新建模拟博客列表数据
[
  {
    "id": 1,
    "catId": 1,
    "catName": "iOS",
    "subCatId": 1,
    "subcatName": "推荐",
    "name": "RxSwift实现MVVM架构",
    "image": "https://images.xiaozhuanlan.com/photo/2018/2f5dff865155d756dfe04f2909cd1a36.png",
    "description": "在本文中,我将介绍iOS编程中的MVVM设计模式,当然还有RxSwift的介绍。本文分为两部分。在第1部分中简要介绍了RxSwift的设计模式和基础知识,在第2部分中 ,我们有一个使用RxSwift的MVVM的示例项目。"
  },
  
  //省略...
]

命名为data.json将其放置在src文件夹下

创建路由信息
// 路由信息
let routes = [
  {
    path: "/",
    name: 'home',
    component: () => import("@/views/All.vue"),
  },
  {
    path: "/ios",
    name: 'ios',
    component: () => import("@/views/iOS.vue"),
  },
  {
    path: "/android",
    name: 'android',
    component: () => import("@/views/Android.vue"),
  },
  {
    path: "/flutter",
    name: 'flutter',
    component: () => import("@/views/Flutter.vue"),
  },
  {
    path: "/web",
    name: 'web',
    component: () => import("@/views/Web.vue"),
  },
];

设置5个路由:全部iOSAndroidFlutterWeb

顶部导航组件
<!-- TheNavigation.vue -->
<template>
  <div id="nav">
    <router-link to="/" class="nav-link">全部</router-link>
    <router-link to="/ios" class="nav-link">iOS</router-link>
    <router-link to="/android" class="nav-link">Android</router-link>
    <router-link to="/flutter" class="nav-link">Flutter</router-link>
    <router-link to="/web" class="nav-link">Web</router-link>
  </div>
</template>

TheNavigation导航组件中有5个

router-link

,分别切换到全部iOSAndroidFlutterWeb

5个页面组件
<template>
  <div class="container">
    <!-- 博客列表 -->
    <div v-for="blog in arrs" class="item" :key="blog.id">
      <!-- 图片 -->
      <img class="thumb" :src="blog.image" />
      <!-- 信息 -->
      <div class="info">
        <div class="title">{{ blog.name }}</div>
        <div class="message"> {{ blog.description }} </div>
      </div>
    </div>
  </div>
</template>

<script setup>

// 数据
import sourceData from "@/data.json";
let arrs = sourceData;

</script>
APP.vue
<script setup>
import TheNavigation from "@/components/TheNavigation.vue";
</script>

<template>
  <TheNavigation />
  <router-view></router-view>
</template>

至此,博客框架就完成了,实现了5个博客分类,效果如下图:

框架

设置

linkActiveClass

路由器可以设置

router-link

激活的类:

const router = createRouter({
  history: createWebHistory(),
  routes,
  <!--// 添加激活的类-->
  linkActiveClass: "blog-active-link"
});

然后设置样式:

#nav .blog-active-link  {
  color: red;
  border-bottom: 2px solid red;
}

linkActiveClass

命名路由

我们在顶部导航组件使用的跳转都是路径跳转

例如:to="/"

, 我们可以给路由设置一个名称

name

,这样可以通过路由的名称

name

进行跳转。

<template>
  <div id="nav">
    <router-link to="/" class="nav-link">全部</router-link>
    <!-- 修改 to 属性为 name -->
    <router-link :to="{name: 'ios'}" class="nav-link">iOS</router-link>
    <router-link :to="{name: 'android'}" class="nav-link">Android</router-link>
    <router-link :to="{name: 'flutter'}" class="nav-link">Flutter</router-link>
    <router-link :to="{name: 'web'}" class="nav-link">Web</router-link>
  </div>
</template>

路由的

query

前面提到的5个博客分类是固定的,我们点击博客列表的每条数据进入博客详情,此时由于不同的博客内容是不同的,所以不能固定写死。实现方法一是通过路由传参实现。

添加博客详情的路由
let routes = [
  //...
  {
    path: '/blogdetail',
    name: "blogdetail",
    component: () => import("@/views/BlogDetail.vue")
  }
];
query

传参

<template>
  <div class="container">
    <!-- 传参 -->
    <router-link v-for="blog in arrs" class="item" :key="blog.id" :to="{ name: 'blogdetail', query: { id: blog.id } }">
      // 省略
    </router-link>
  </div>
</template>

设置

query: { id: blog.id } }

给路由传参

接收
query

传参

<template>
  <div class="container">
    <h2>{{ blog.name }}</h2>
    <p>{{ blog.description }}</p>
  </div>
</template>

<script>
import sourceData from "@/data.json";
import { useRoute } from "vue-router";
export default {
  setup(props) {
    // 获取路由
    let route = useRoute();
    // 获取query参数
    let blogId = route.query.id;

    return {
      blog: sourceData.find((blog) => blog.id == blogId),
    };
  },
};
</script>

通过

route.query.id

就能获取到传递的博客id, 然后就能显示对应的博客信息了。

query

动态路由

博客详情的页面逻辑,也可以用动态路由去实现。

修改博客详情的路由
<!-- router.js -->
let routes = [
  //...
  {
    <!-- 动态路由路径 -->
    path: '/blogdetail/:id',
    name: "blogdetail",
    component: () => import("@/views/BlogDetail.vue")
  }
];
:id

表示 路由的路径是动态的,路径最后表示博客id.

传参
<template>
  <div class="container">
    <!-- 传参 -->
    <router-link v-for="blog in arrs" class="item" :key="blog.id" :to="{ name: 'blogdetail', params: { id: blog.id } }">
      // 省略
    </router-link>
  </div>
</template>

设置

params: { id: blog.id } }

给动态路由传参

接收参数
let blogId = route.params.id;

通过

route.params.id

就能获取到传递的博客id, 然后就能显示对应的博客信息了。

重命名路由

知道了动态路由的逻辑后,我们当然可以把iOS, Android, Flutter, Web四个页面合并为一个页面。

合并
router
<!-- router.js -->
let routes = [
  {
    path: "/",
    name: 'home',
    component: () => import("@/views/All.vue"),
  },
  <!-- 将/ios,/android,/flutter,/web四个合并为/category/:catId -->
  {
    path: "/category/:catId",
    name: 'category',
    component: () => import("@/views/All.vue"),
  },
  {
    path: '/blogdetail/:id',
    name: "blogdetail",
    component: () => import("@/views/BlogDetail.vue")
  }
];
修改导航
<template>
  <div id="nav">
    <router-link to="/" class="nav-link">全部</router-link>
    <!-- 动态路由 -->
    <router-link :to="{name: 'category', params: { catId: 1 }}" class="nav-link">iOS</router-link>
    <router-link :to="{name: 'category', params: { catId: 2 }}" class="nav-link">Android</router-link>
    <router-link :to="{name: 'category', params: { catId: 3 }}" class="nav-link">Flutter</router-link>
    <router-link :to="{name: 'category', params: { catId: 4 }}" class="nav-link">Web</router-link>
  </div>
</template>
列表
<script setup>
import { useRoute } from 'vue-router';

// 获取路由
let route = useRoute();
// 获取params参数
let catId = route.params.catId;

// 数据
import sourceData from "@/data.json";
let arrs = sourceData.filter((blog) => blog.catId == catId);

</script>

这样我就可以把

iOS.vue

,

Android.vue

,

Flutter.vue

,

Web.vue

四个组件文件删除了。

你应该有个疑问,home路由的内容其实和category路由的内容也是一样的,是否可以合并呢?

重命名"/"

可以将"/“重命名为’/category/0’,这样所有的5个路由都将访问”**/category/:catId**"这个路由了。

<!-- router.js -->
let routes = [
  {
    path: "/",
    <!-- 重命名 -->
    redirect: '/category/0'
  },
  {
    path: "/category/:catId",
    name: 'category',
    component: () => import("@/views/All.vue"),
  },
  {
    path: '/blogdetail/:id',
    name: "blogdetail",
    component: () => import("@/views/BlogDetail.vue")
  }
];
import sourceData from "@/data.json";
let arrs = catId != '0' ? sourceData.filter((blog) => blog.catId == catId) : sourceData;

判断下,如果

catId != '0'

为分类筛选,否则就是显示全部

监听路由变化

此时的代码出现了问题,点击顶部的导航切换不同的分类,底下的列表将不会变化。这是因为组件复用了。此时需要监听组件的路由的变化,切换数据。

路由

可以通过

watch

函数监听

route.params

, 当路由变化后,就可以重新获取数据。

<!-- All.vue -->
<script setup>
import { ref } from '@vue/reactivity';
import { useRoute } from 'vue-router';
import sourceData from "@/data.json";
import { watch } from '@vue/runtime-core';

let arrs = ref([]);

let route = useRoute();
let params = route.params;

let initData = (catId) => {
  arrs.value = catId != '0' ? sourceData.filter((blog) => blog.catId == catId) : sourceData;
}

// 初始化的时候获取数据
initData(params.catId);

// 监听paramas,更新数据
watch(() => route.params.catId, (value) => {
  initData(value);
})

</script>

禁止路由复用

解决上节问题,还有一个更简单的方法,就是禁止路由的复用。

<template>
  <TheNavigation />
  <!-- 禁止路由复用 -->
  <router-view :key="$route.path"></router-view>
</template>

通过这个方法,动态组件将不会复用,直接卸载旧组件,挂载新组件。所以性能上有丢丢的损耗。

给组件传递

props

我们前面在组件中需要使用

useRoute

获取到路由,然后获取对应的

route.params

, 我们可以通过另外一种方式获取

route.params

路由添加
props

属性

<!-- router.js -->
{
    path: "/category/:catId",
    name: 'category',
    component: () => import("@/views/All.vue"),
    <!-- 路由添加`props`属性 -->
    props: true,
}
组件中获取
props

属性

<script setup>
import { ref } from '@vue/reactivity';
import { useRoute } from 'vue-router';
import sourceData from "@/data.json";

// 定义props
const props = defineProps({
  catId: {
    type: String,
    required: true,
  }
})

let arrs = props.catId != '0' ? sourceData.filter((blog) => blog.catId == props.catId) : sourceData;

</script>

组件中可以直接获取到

catId

参数,个人认为这种写法更优美。

路由
props

属性支持函数

<!-- router.js -->
{
    path: "/category/:catId",
    name: 'category',
    component: () => import("@/views/All.vue"),
    props: route => ({ catId: parseInt(route.params.catId) }) ,
}

函数中,可以对参数进行处理,我们的例子中是将

catId

从字符串变成了数字

// 定义props
const props = defineProps({
  catId: {
    type: Number,
    required: true,
  }
})

let arrs = props.catId !== 0 ? sourceData.filter((blog) => blog.catId === props.catId) : sourceData;
props catId

的定义和使用也要进行相应的修改

编程式导航

除了使用

<router-link>

来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

例如:可以在详情页加一个按钮,点击返回上一个页面

<button @click="$router.back()">返回</button>

转场动画

Vue Router4 的转场动画的实现 和 以前的版本有些不一致。需要将

transition

包含在

router-view

, 如下所示:

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

加上对应的css样式

/* fade 模式 name="fade" mode="out-in" */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

这样切换就有淡入淡出的效果了。效果自定义,很方便。

路由未匹配上

有时候用户可能输入一个根本不存在的路劲(例如:http://localhost:3000/categorys),此时最好是给显示个默认的404页面,这样用户体验更好。

404页面
定义路由
<!-- router.js -->
{
    path: '/:pathMatch(.*)*',
    name: "NotFound",
    component: () => import("@/views/404.vue"),
}

注意,这个路由一定要放在最后,否则就有问题了。

404页面
<template>
  <div class="container">
    <h2>未找到页面</h2>
  <router-link to="/">回到首页</router-link>
  </div>
</template>

这个页面内容随意

路由守卫

路由独享的守卫

想象下用户浏览器地址栏输入

http://localhost:3000/category/6

, 其实也会出现一些问题,因为不存在这个分类。这时候需要进行处理, 当分类不存在的时候跳转到404页面。

<!-- router.js -->
  {
    path: "/category/:catId",
    name: 'category',
    component: () => import("@/views/All.vue"),
    props: route => ({ catId: parseInt(route.params.catId) }),
    <!-- 添加路由守卫 -->
    beforeEnter: (to, from) => {
      // 如果不是正确的分类,跳转到NotFound的页面
      console.log(to.params.catId);
      if (!["0", "1", "2", "3", "4"].includes(to.params.catId)) { 
        return {
          name: "NotFound",
          // 这个是在地址栏保留输入的信息,否则地址栏会非常的丑
          params: { pathMatch: to.path.split("/").slice(1) },
          query: to.query,
          hash: to.hash,
        };
      }
    }
  },

判断如果不是正确的分类,跳转到NotFound的页面

路由全局守卫

在某些路由中需要一些特定的操作,譬如访问前必须是登录用户。这时候可以通过使用

meta

属性和全局守卫来实现。

譬如有一个课程专栏我们设置为需要用户登录才能访问。我们可以如下设置

<!-- router.js -->
  {
    path: '/course',
    name: "course",
    component: () => import("@/views/Course.vue"),
    <!-- 需要登录 -->
    meta: {needLogin: true}
  },
  {
    path: '/login',
    name: "login",
    component: () => import("@/views/Login.vue"),
  },
  

添加一个全局守卫, 需要登录但是没有登录的情况下就跳转到登录页面

<!-- router.js -->
// 全局守卫
router.beforeEach((to, from) => {
  if (to.meta.needLogin && !userLogin) {
    // need to login
    return { name: "login" };
  }
});
组件内的路由守卫

前面的切换分类的章节的问题其实还有第三者解决方案,就是用组件内的路由守卫。

<script setup>
import { ref } from '@vue/reactivity';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import sourceData from "@/data.json";

// 定义props
const props = defineProps({
  catId: {
    type: Number,
    required: true,
  }
})

let arrs = ref([]);

let fetchData = (id) => {
  return id !== 0 ? sourceData.filter((blog) => blog.catId == id) : sourceData;
}

<!-- 组件内的路由守卫 -->
onBeforeRouteUpdate((to, from, next) => {
  arrs.value = fetchData(to.params.catId)
});

arrs.value = fetchData(props.catId);

</script>

对于一个带有动态参数的路径

/category/:catId

,在

/category/1

/category/2

之间跳转的时候, 会触发

onBeforeRouteUpdate

的路由钩子函数,直勾勾自函数中可以进行数据的更新。

扩展 RouterLink

router-link

可以实现路由的跳转,此外为了更加丰富功能,可以对其进行扩展。譬如我们可以扩展实现能够跳转到外部链接。

<!--AppLink.vue-->
<template>
  <!-- 如果是外部链接,跳转(<slot />表示router-link组件中的slot内容)  -->
  <a v-if="isExternal" :href="to"><slot /></a>
  <!-- 如果是APP内的链接,路由跳转 (<slot />表示router-link组件中的slot内容) -->
  <router-link v-else v-bind="$props"><slot /></router-link>
</template>

<script>
import { computed, defineComponent } from "@vue/runtime-core";
import { RouterLink } from "vue-router";

export default {
  props: {
    // 继承RouterLink的props
    ...RouterLink.props,
  },
  setup(props) {
    
    // 如果`to`属性值是字符串类型,并且以`http`开头,我们认为它是外部链接
    let isExternal = computed(() => typeof props.to === 'string' && props.to.startsWith('http'));

    return {
      isExternal
    }
  }
};
</script>

使用:

<AppLink to="https://www.domain.cn" />

总结

Vue Router 4.x 的使用基本上介绍完了,最重要的特性是能和Composition API的搭配使用,此外使用上也还是有一些不小的变化。

标签: vue.js 前端 vscode

本文转载自: https://blog.csdn.net/lcl130/article/details/122800643
版权归原作者 JonnyLan 所有, 如有侵权,请联系我们删除。

“Vue Router 4 的使用,一篇文章给你讲透彻”的评论:

还没有评论