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.vueAbout.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-linkrouter-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中所有的组件都会被一次加载。我们的例子中就是 HomeAbout组件,即使有时候不会用到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;
}

命名路由

我们在顶部导航组件使用的跳转都是路径跳转例如: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, 然后就能显示对应的博客信息了。

动态路由

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

修改博客详情的路由
<!-- 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 loginreturn { 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 Router 4 的使用,一篇文章给你讲透彻相关推荐

  1. list vue 添加数据方法_一篇文章教会你创建vue项目和使用vue.js实现数据增删改查...

    简介:一篇文章教会你创建vue项目和使用vue.js实现数据增删改查 [一.项目背景] 在管理员的一些后台页面里,数据列表中都会对这些数据进行增删改查的操作,例如管理员添加商品.修改商品价格.删除商品 ...

  2. 关于主从延迟,一篇文章给你讲明白了!(转)

    生活中所受的苦,终会以一种形式回归! 前言 在实际的生产环境中,由单台MySQL作为独立的数据库是完全不能满足实际需求的,无论是在安全性,高可用性以及高并发等各个方面 因此,一般来说都是通过集群主从复 ...

  3. 一篇文章教会你创建vue项目和使用vue.js实现数据增删改查

    简介:一篇文章教会你创建vue项目和使用vue.js实现数据增删改查 [一.项目背景] 在管理员的一些后台页面里,数据列表中都会对这些数据进行增删改查的操作,例如管理员添加商品.修改商品价格.删除商品 ...

  4. [Vue 牛刀小试]:第十六章 - 针对传统后端开发人员的前端项目框架搭建

    一.前言 在之前学习 Vue 基础知识点的文章中,我们还是采用传统的方式,通过在 html 页面上引用 vue.js 这个文件,从而将 Vue 引入到我们的项目开发中.伴随着 Node.js 的出现, ...

  5. vue项目没有router文件夹_vue项目——Vue Router路由的使用

    前言: 学习vue也有一段时间了,这里把学习整个vue项目中的一些知识点和问题,整理下方便自己记录,也希望给大家带来方便.我会连续写几篇系统的文章,讲解一个完整的vue项目中用到的各个使用模块.今天先 ...

  6. 你可能不清楚的 Vue Router 深度用法(一)

    Vue Router 简单易上手,能实现大部分的需求.但是,如果在项目里需要更细致的控制路由,以实现与其同步的效果,就需要挖掘其文档里没详细提及的内容.第一章为路由元信息用途挖掘. 路由元信息用途 ( ...

  7. 一篇文章带你快速入门JavaScript(自学者福利)

    文章目录 一.开始之前 二.数据类型 1.变量 2.数字类型(number) 3.字符串类型(string) 4.布尔型.Null和Undefined 5.数据类型转换 三.运算符 四.控制语句 五. ...

  8. 【跨域】一篇文章彻底解决跨域设置cookie问题!

    一篇文章彻底解决跨域设置cookie问题! 大家好我是雪人~~⛄ 之前做项目的时候发现后端传过来的 SetCookie 不能正常在浏览器中使用. 是因为谷歌浏览器新版本Chrome 80将Cookie ...

  9. Vue3 +TypeScript 引入 BabylonJs(Vue3实现3D)【一篇文章精通系列】

    本文主要介绍如何使用Vue3和TypeScript引入BabylonJs技术实现3D效果.结合实际案例,详细讲解了如何在Vue3项目中引入BabylonJs,并了解其相关知识.通过本文的学习,相信读者 ...

最新文章

  1. 汉字转换成全拼的拼音
  2. Python3入门笔记(1) —— windows安装与运行
  3. Fiddler之Autoresponder替换(Web)
  4. Github标星24.9k!适合初学者的有趣、入门级的开源项目
  5. Android-service
  6. LeetCode283——Move Zeroes(将0移动到数组最后面)
  7. 事务回退机制 android,【Android基础】——Fragment-使用方法
  8. 在JSP页面中,对同名的CHECKBOX的处理
  9. Postman的使用说明
  10. QT+OPENCV+FFTW内存问题
  11. Python实现的文件夹同步
  12. linux设备驱动之 i2c设备驱动 at24c08驱动程序分析【全部地址的操作】
  13. JS 转换民族国标码(数字码和英文码)
  14. 2020计算机校友会大学排名,2020年校友会大学排名:一个世界一流大学,一个中国一流大学...
  15. android 分享到qq黑屏,Android 第三方登录 QQ登录Android 10系统 出现黑屏问题
  16. 如何是matlab中的折线图变得更加的光滑?
  17. OSChina 周三乱弹 —— 谈什么对象?睡什么觉?
  18. OSPF(三)OSPF域内路由
  19. 感悟 | 电影《你的名字》
  20. ESP8266 WIFI模块学习之路(2)——模块与单片机连接进行远程操作

热门文章

  1. 整理一站式积分兑换商城框架方案
  2. 液晶屏问题诊断:液晶屏缺陷检测算法总结
  3. 使用WebView时错误:WebPage not available
  4. 偏度(skewness)
  5. python中opener_Python中的opener()方法是什么
  6. 【OpenCV 例程200篇】22. 图像添加非中文文字(cv2.putText)
  7. C和Java对比,哪个更好一些?
  8. php通过谷歌身份验证实现动态口令
  9. 区块链安全100问 |​ 第四篇:保护数字钱包安全,防止资产被盗
  10. 文字在线中间,CSS巧妙实现分隔线的几种方法