【前端】VueRouter4(超详细!!)

Jim.KK 2024-07-20 16:33:01 阅读 67

VueRouter4学习笔记

本文同步学习视频来自于Vue官方推荐课程 —— VueSchool 视频地址

刚发布了Vuex的学习笔记(上周的产物),而这篇笔记是上上周的产物,写完后就放在Git里了,现在整理下发出来,创作不易,求关注!

文章目录

VueRouter4学习笔记第一集第二集第三集代码结论

第四集第五集第六集第七集 | 关于 History Mode第八集 | 懒加载情况一 | 加载全部页面情况二 | 懒加载

第九集 | 将导航分离为组件第十集 | 选中的导航样式第十二集 | 新增Destination页面第十三集 | 使用route.name进行跳转Home.vue 页面代码TheNavigation.vue 页面代码

第十四集 | 网络请求解决方式一 | `$watch`监听器解决方式二 | 在`router-view`中加入key

第十五集 | router传参情况一 | 在接收参数时修改参数类型情况二 | 在router中修改参数类型情况二 的 补充总结

第十六集ExperienceCard页面DestinationShow页面ExperienceShow 页面route/index.js文件

第十七集 | 内嵌式页面第十八集 | GoBack按钮第十九集 | 页面跳转动画moveUp 效果VUE2 | router-view 写法Vue3 | router-view 写法添加过渡效果Target 放在同一个div中

移出效果App.vue 中修改 router-viewApp.vue 中添加CSS过渡效果

第二十集 | 404 Not Found 页面创建`views/NotFound.vue`页面增加路由

第二十一集 | 404页面优化第二十二集 | 页面切换时回到顶部第二十三集 | 受保护的页面(登录才能使用)Protected 页面Login 页面路由信息

第二十四集 | 重定向需求说明代码变动 1 | 添加Invoices页面代码变动 2 | Login页面代码变动代码变动 3 | router/index.js

第二十五集 | 自定义AppLinkAppLink全局注册使用代码说明疑惑

第二十六集 | 使用组合式 API使用组合式API(setup)实现登录使用组合式API实现跳转页面前确认

额外知识 1 (Part5) | $router.push 与 replace额外知识 2 (Part5) | 多个子组件实现特定页面显示组件新增加的导航组件代码在路由中注册该组件

补充知识 3 (Part 5) | 地址重定向 与 重命名重命名 | 使用alias重命名重定向 1 | 直接引入路径重定向 2 | 用页面名称重定向到某个页面重定向 3 | 使用一个方法同时定义多个重定向与重命名当重定向与重命名冲突

补充知识 4 (Part5) | 导航失效补充知识 5 (Part5) | 使用正则表达式匹配路由示例一 | 使用 + 匹配多个数字示例二 | 使用双 + 匹配多个`/数字`(重要)实例三 | 使用 * 表示0个或多个实例四 | 使用 ? 匹配或一个示例五 | 多个路由匹配

补充知识 6 | 动态添加/删除路由动态添加路由动态删除路由

额外的奖励 1 | Router的传参方式

第一集

创建项目

<code>## 创建项目

npm create vite@latest vue-school-travel-app -- --template vue

## 进入项目目录

cd vue-school-travel-app

## 安装依赖

npm install

## 启动项目

npm run dev

第二集

介绍了项目目录结构

vite.config.js中添加了内容创建了jsconfig.json文件创建了views文件夹

第三集

代码

main.js中添加了vue-router等内容,并且createApp(APP).use(router).mount('#app')

import { createApp } from 'vue'

import './style.css'

import App from './App.vue'

// Vue-Router

引入页面(使用Vite一定要加后缀)

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

import Home from '@/views/Home.vue'

import About from '@/views/About.vue'

const router = createRouter({

history: createWebHistory(),

// 通过添加记录的方式使用router

routes: [

{ path: '/',name: 'Home',component: Home},

{ path: '/about',name: 'About',component: About}

]

})

// 创建一个APP mount to DOM

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

创建了两个页面 Home/About 并且注册到main.js的router中(上述代码包含)

App.vue中添加了<router-link><router-view>标签,以显示内容

结论

<router-view>标签是一个功能标签,可以控制页面出现的位置<router-link to="">code>本质上是一个a标签,但是当我们点下该标签时,Vue会拦截我们的点击事件,以阻止页面刷新,a标签可以起到相似的效果,但是会刷新页面。内部页面使用<router-link to="">code>,外部页面使用<a href="">code>

第四集

Babel 会给我们提供最新的JavaScript支持

第五集

重要:Vue是一个单页面应用,我们做的只是修改APP.vue中的样式。

第六集

添加了data.jsonmain.css文件与images文件夹创建了几个地址.vue文件重新定义了几个router(主要指向这几个地址文件)在App.vue中定义了一个顶部导航栏在Home.vue中通过导入data.json的方式,定义了四个带标题与名称的导航页

第七集 | 关于 History Mode

我们有两种router模式:

createWebHashHistorycreateWebHistory

通过上面的模式,我们可以在单页面应用中模拟HTML5的通过路径去寻找index.html的方式

下面的方式,就是实实在在的通过HTML5的方式去寻找index.html路径

第八集 | 懒加载

如果采用import + Component的方式,那么所有的页面都会被打包进index.xxx.js文件,这样我们的浏览器在访问页面的时候,会一次性加载所有的内容,这样会导致我们的页面非常的庞大。

如果想要采用懒加载的方式,我们只需要采用component: ()=>import();的方式,这样的话打包的时候会对每个懒加载的页面生成一个单独的js文件,我们加载页面的时候不会一次性加载所有的vue文件,只有当我们点进某个页面的时候,才会加载该页面。

情况一 | 加载全部页面

import Home from '@/views/Home.vue'

import About from '@/views/About.vue'

import Brazil from '@/views/Brazil.vue'

import Hawaii from '@/views/Hawaii.vue'

import Jamaica from '@/views/Jamaica.vue'

import Panama from '@/views/Panama.vue'

// 单独分离出来

const routes = [

{ path: '/',name: 'Home',component: Home},

{ path: '/about',name: 'About',component: About},

{ path: '/brazil',name: 'Brazil',component: Brazil},

{ path: '/hawaii',name: 'Hawaii',component: Hawaii},

{ path: '/jamaica',name: 'Jamaica',component: Jamaica},

{ path: '/panama',name: 'Panama',component: Panama}

]

非懒加载打包后如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

情况二 | 懒加载

<code>const routes = [

{ path: '/',name: 'Home',component: Home},

{ path: '/about',name: 'About',component: About},

{ path: '/brazil',name: 'Brazil',component: ()=>import('@/views/Brazil.vue')},

{ path: '/hawaii',name: 'Hawaii',component: ()=>import('@/views/Hawaii.vue')},

{ path: '/jamaica',name: 'Jamaica',component: ()=>import('@/views/Jamaica.vue')},

{ path: '/panama',name: 'Panama',component: ()=>import('@/views/Panama.vue')}

]

打包后内容如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意:仅仅通过方式二还不能实现懒加载,如果要懒加载一定不能再上面<code>import这个页面

第九集 | 将导航分离为组件

components下面创建TheNavigation组件,并写入以下代码

<template>

<div id="nav">code>

<router-link to="/">Home</router-link>code>

<!-- router-link 会拦截浏览器重新加载页面的行为 -->

<router-link to="/brazil">Brazil</router-link>code>

<router-link to="/hawaii">Hawaii</router-link>code>

<router-link to="/jamaica">Jamaica</router-link>code>

<router-link to="/panama">Panama</router-link>code>

</div>

</template>

删除掉App.vue中的上述代码,并通过script引入该组件

<script>

import TheNavigation from '@/components/TheNavigation.vue';

export default {

components: {

TheNavigation

}

}

</script>

在App.vue最上面使用组件

<TheNavigation/>

效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第十集 | 选中的导航样式

在<code>router/index.js中的createRouter中添加了以下内容,让被选中的导航可以改变颜色样式等:

const router = createRouter({

history: createWebHashHistory(),

routes,

// 添加的内容

linkActiveClass: 'vue-school-active-link'

})

components/TheNavigation.vue中添加style定义样式:

<style scoped>

.vue-school-active-link {

color: red;

border: 1px solid red;

}

</style>

第十二集 | 新增Destination页面

通过Destination页面,我们可以查看每一个景点的内容,只需要在地址栏输入该景点所对应的ID即可

本章节使用到了地址传参技术,通过以下代码(路由),我们可以定义一个带参数的页面:

{ path: '/destination/:id',component: import('@/views/DestinationShow.vue')}

我们可以在页面中通过$route.params.id获取页面输入的属性值,也可以通过计算属性computed,将该地址值变为一个参数。

computed: {

destinationId() {

return parseInt(this.$route.params.id)

},

destination() {

return sourceData.destinations.find(destination => destination.id === this.destinationId)

}

}

第十三集 | 使用route.name进行跳转

除了使用route.path地址进行跳转以外,我们还可以使用route.name进行跳转,route.name可以避免我们在大型项目中,path需要频繁更换的问题(官方课程中,称使用name跳转是:规则的改变者)。

举个例子,我们有个100页面的项目,这些页面随着开发,需要频繁的更换URL,此时我们需要更新route路由以及页面中所有用到这个path的地方,但是我们若是使用name进行跳转的话,我们仅仅需要更新route路由即可。

Home.vue 页面代码

<template>

<div class="home">code>

<h1>All Destinations</h1>

<div class="destinations">code>

<!-- 注意下面的代码 -->

<router-link

v-for="destaination in destinations"code>

:key="destaination.id"code>

:to="{name: 'destaination.show',params: {id: destaination.id}}">code>

<h2>{ { destaination.name }}</h2>

<img :src="`/images/${destaination.image}`" :alt="destaination.name"/>code>

</router-link>

</div>

</div>

</template>

TheNavigation.vue 页面代码

随后修改TheNavigation.vue页面的导航内容,也是用上面相通的方式:

<template>

<div class="home">code>

<h1>All Destinations</h1>

<div class="destinations">code>

<router-link

v-for="destaination in destinations"code>

:key="destaination.id"code>

:to="{name: 'destaination.show',params: {id: destaination.id}}">code>

<h2>{ { destaination.name }}</h2>

<img :src="`/images/${destaination.image}`" :alt="destaination.name"/>code>

</router-link>

</div>

</div>

</template>

<script>

import sourceData from '@/data.json'

export default {

data() {

return {

destinations: sourceData.destinations

}

}

}

</script>

至此,我们的页面已经不在需要单独的风景.vue页面以及link,我们只需要通过在Home页面点击上方的导航栏,或者下面的图片链接,我们就可以跳转到某个页面中去,唯一的小缺点就是,我们现在访问的路径是/destaination/id,这看上去不是很友好。所以,我们又在route上加上了slug,仅需要在导航与Home上加上slug参数即可,其它不用改变,这样一来,导航栏就会是/destaination/{id}/{slug}的方式。

第十四集 | 网络请求

将内容读取从本地json问阿金替换为网络请求之后,我们使用created来让页面首次加载的时候可以请求内容。

但是,问题来了!由于我们不像是之前一样为每一个景区定义一个页面(之前是每次进入新的页面都会执行created里面的方法),但是现在所有的景区都在同一个页面中,我们从a景区点击进入b景区并不会重新执行页面,所以内容无法刷新。

为了解决这一问题,我们可以通过下面的方式,在页面中加入一个$watch,来监听页面params的变化,每次发生变化的时候重新执行一次请求。

解决方式一 | $watch监听器

export default {

data() {

return {

// 变量

destination: null

}

},

computed: {

// 计算属性,ID

destinationId() {

return parseInt(this.$route.params.id)

},

},

methods: {

// 单独分离出来的请求方法

async initData() {

const response = await fetch(`https://travel-dummy-api.netlify.app/${ this.$route.params.slug}`)

this.destination = await response.json()

}

},

// 进入页面后执行的第一个方法 -> 该方法还会创建一个监听器,当url发生变化的时候去执行该方法

async created() {

this.initData()

this.$watch(() => this.$route.params,this.initData)

}

}

这样一来,我们就可以解决上面的问题。

解决方式二 | 在router-view中加入key

通过下面的方式,我们也可以实现上面的效果,与上面的方式相比简单了许多。

<router-view :key="$route.path"></router-view>code>

解释:key是一个特殊的组件,当key值改变的时候,key值所在的组件会被重新渲染,所以这里也能达到相同的效果。

第十五集 | router传参

情况一 | 在接收参数时修改参数类型

现在我们router中的代码如下所示,当我们使用router进入页面的时候,我们得到的其实是一个字符串id(尽管它看起来很像是数字),这时候我们在接收参数的时候,需要手动的修改参数的类型,代码如下2所示。

代码一:router代码

const routes = [

{ path: '/',name: 'Home',component: Home},

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue')

}

]

代码二:接收参数

computed: {

destination() {

return sourceData.destinations.find(destination => destination.id === parseInt(this.$route.params.id))

}

}

当我们接收参数的时候,我们不得不使用parseInt()方法来将字符串类型转换为数字类型。但是如果我们使用该参数的地方很多的话,这样一一修改无疑是很麻烦的,解决方案一就是我们在该页面中定义一个计算属性,来获取当前页面的ID,这样我们可以将每次传递进来的参数都转化为一个Number来使用。

computed: {

id() {

return parseInt(this.$route.params.id);

},

destination() {

return sourceData.destinations.find(destination => destination.id === this.id )

}

}

情况二 | 在router中修改参数类型

以上代码虽然可以达到效果,但是这个计算属性id多少有些冗余,如果我们有多个如此的参数(n个的话),我们的代码量似乎要增加3n行,这大大降低了代码的美观度,有没有什么方法可以解决呢?请看下面的代码!

// router/index.js

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue'),

props: route=> ({ id: parseInt(route.params.id)})

}

在上述的代码中,我们定义了一个props,并且将id(来自'/destination/:id/:slug')使用parseInt后放在了props中,这样一来,我们页面可以使用props来接收参数,接收到的参数类型将会是Int类型,无需再次变型。

在页面中,我们只需要像下面一样接收参数即可:

// 使用Props传参,可以直接当变量使用

props: {

id: { type: Number,required: true},

},

computed: {

destination() {

return sourceData.destinations.find(destination => destination.id === this.id)

}

}

上述代码中描述我们要调用该页面,有一个必输项Number类型的id需要传入,拿到这个id之后我们我们的页面才会加载

请注意:此时的页面中我们依旧可以通过this.$route.params.id的方式获取到地址中的id类型,不过它仍旧是String类型,要是用的话我们还是需要手动转变,而props中的参数则是重新开辟了一条路径,两者并不相干。

情况二 的 补充

正如上面所属,情况二我们实际上是重新开辟了一条路径,props参数与地址中的参数并不冲突,且可以随意改变,下面的代码中我们在props中定义的id与原id存在一个差值(+1),这样我们使用props进入网页的时候,每次都会访问错误的内容。

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue'),

props: route=> ({ id: parseInt(route.params.id)+1})

}

效果如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

另外,我们也可以传入多个props值,就像是下面一样:

<code>// 传入值

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue'),

props: route=> ({ id: parseInt(route.params.id),newsletterPopup: false})

}

// 页面中接受

props: {

id: { type: Number,required: true},

newsletterPopup: { type: Boolean,required: true}

},

这样一来,我们的页面就总是会接收一个值为false的newsletterPopup参数

总结

本章节主要是说除了原始的通过地址值传参数以外,我们还可以通过props方式传递参数,且props方式可以在router中使用地址值传参中的参数,这样一来props方式就方便了很多。

第十六集

实际第18集

DestinationShow页面下添加了ExperienceCard菜单栏,相当于我们可以进入Destination查看某个地方的简介,下面的导航栏是详细的景区内容,点进去之后是更详细的内容。

当前的页面结构比较简单,从主页 点击DestinationCards -> 进入 DestinationShows Page -> 点击 ExperienceCards -> 进入 ExperienceShow Page

注意:这一板块的props代码中采用了...route的方式来将route Object拆分成{id: x,slug: xxx,experienceSlug: xxxx}的形式

ExperienceCard页面

<template>

<div class="card">code>

<img :src="`/images/${experience.image}`" :alt="experience.name"/>code>

<span class="card__text">code>

{ {experience.name}}

</span>

</div>

</template>

<script>

export default {

props: {

experience: {type: Object,required: true}

}

}

</script>

DestinationShow页面

<template>

<section v-if="destination" class="destination">code>

<h1>{ { destination.name }}</h1>

<div class="destination-details">code>

<img :src="`/images/${destination.image}`" :alt="destination.name">code>

<p>{ {destination.description}}</p>

</div>

</section>

<!-- 新增的section -->

<section class="experiences">code>

<h2>Top Experiences in { { destination.name }}</h2>

<div class="cards">code>

<router-link

v-for="experience in destination.experiences"code>

:key="experience.slug"code>

:to="{name: 'experience.show', params: {experienceSlug: experience.slug}}"code>

>

<ExperienceCard

:experience="experience"code>

/>

</router-link>

</div>

</section>

</template>

<script>

import sourceData from '@/data.json';

import ExperienceCard from "@/components/ExperienceCard.vue";

export default {

components: {ExperienceCard},

// 使用Props传参,可以直接当变量使用

props: {

id: {type: Number,required: true}

},

computed: {

destination() {

return sourceData.destinations.find(destination => destination.id === this.id)

}

}

}

</script>

ExperienceShow 页面

<template>

<section>

<h1>{ {experience.name}}</h1>

<img :src="`/images/${experience.image}`" :alt="experience.name"/>code>

<p>{ {experience.description}}</p>

</section>

</template>

<script>

import sourceData from '@/data.json'

export default {

props: {

id: {type: Number,required: true},

experienceSlug: {type: String,required: true}

},

computed: {

destination() {

return sourceData.destinations.find(

destination => destination.id === this.id

)

},

experience() {

return this.destination.experiences.find(

experience => experience.slug === this.experienceSlug

)

}

}

}

</script>

route/index.js文件

// 新增路由

{

path: '/destination/:id/:slug/:experienceSlug',

name: 'experience.show',

component: () => import('@/views/ExperienceShow.vue'),

props: route => ({ ...route.params,id: parseInt(route.params.id)})

}

第十七集 | 内嵌式页面

项目至此,我们已经有了一个router-view标签,但是此时若是我们想将ExperienceShow Page放到DestinationShow Page下面,我们还需要一个router-view标签。App.vue中的router-view标签我们称为根显示标签,这第二层标签就是在其下的一个显示标签。

代码改动较小,值变化了route/index.js部分

改动前:

const routes = [

{ path: '/',name: 'Home',component: Home},

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue'),

props: route=> ({ ...route.params,id: parseInt(route.params.id)})

},

{

path: '/destination/:id/:slug/:experienceSlug',

name: 'experience.show',

component: () => import('@/views/ExperienceShow.vue'),

props: route => ({ ...route.params,id: parseInt(route.params.id)})

}

]

改动后:

const routes = [

{ path: '/',name: 'Home',component: Home},

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue'),

props: route=> ({ ...route.params,id: parseInt(route.params.id)}),

children: [

{

path: ':experienceSlug',

name: 'experience.show',

component: () => import('@/views/ExperienceShow.vue'),

props: route => ({ ...route.params,id: parseInt(route.params.id)})

}

]

}

]

可以看到,我们给父组件的router添加了一个children列表,然后内部可以加上我们需要的组件地址,其中组件的URL可以省略父组件中重合的URL

第十八集 | GoBack按钮

在Vue中,我们可以通过$router.back()来实现返回上一级页面,该函数的作用与我们在浏览器(左上角)点击返回是同样的效果。

假设我们当前的页面跳转顺序为 a -> b -> c -> d -> e,那么我们从e页面依次back的顺序就是 d -> c -> b -> a

在代码中,我们新建components/GoBack.vue组件

<template>

<span class="go-back">code>

<button @click="$router.back()">go back</button>code>

</span>

</template>

随后,我们在views/DestinationShow.vue页面中使用该按钮:

<template>

<section v-if="destination" class="destination">code>

<h1>{ { destination.name }}</h1>

<!-- 使用组件 -->

<GoBack/>

<div class="destination-details">code>

<img :src="`/images/${destination.image}`" :alt="destination.name">code>

<p>{ {destination.description}}</p>

</div>

</section>

<section class="experiences">code>

<h2>Top Experiences in { { destination.name }}</h2>

<div class="cards">code>

<router-link

v-for="experience in destination.experiences"code>

:key="experience.slug"code>

:to="{name: 'experience.show', params: {experienceSlug: experience.slug}}"code>

>

<ExperienceCard

:experience="experience"code>

/>

</router-link>

</div>

<router-view/>

</section>

</template>

<script>

import sourceData from '@/data.json';

import ExperienceCard from "@/components/ExperienceCard.vue";

// 导入组件

import GoBack from "@/components/GoBack.vue";

export default {

// 定义组件

components: {ExperienceCard,GoBack},

// 使用Props传参,可以直接当变量使用

props: {

id: {type: Number,required: true}

},

computed: {

destination() {

return sourceData.destinations.find(destination => destination.id === this.id)

}

}

}

</script>

第十九集 | 页面跳转动画

当前的页面情况(只考虑上层)是:我们从APP.vue(导航页面)可以进入到DescriptionShow页面,但是页面间的跳转效果比较生硬,我们可以在页面跳转的时候加上一些动画,且页面间的元素必须符合以下要求:

父页面的跳转元素(router-view)与子页面的整个页面,都需要包含在一个单独的标签中(父页面不用说,子页面需要有一个div包含)父页面需要添加<transition>标签,并给这个标签一个名称,随后在css中根据这个名称定义过渡类型当我们定义了过渡效果的css之后,在页面过渡的时候Vue会检查目标页面是否有过渡效果,如果存在的话,那么该过渡效果会在页面切换的时候被添加到目标页面的class上去,并在结束后移除

moveUp 效果

VUE2 | router-view 写法

在Vue2中我们仅需要将router-view标签放进transition标签中即可

<transition name="moveUp">code>

<router-view></router-view>

</transition>

Vue3 | router-view 写法

Vue3中我们需要router-view中包含一个transition标签中包含一个component,并用v-slot/is绑定router-viewcomponent

<router-view v-slot="{Component}">code>

<transition name="moveUp">code>

<component :is="Component" :key="$route.path"></component>code>

</transition>

</router-view>

以上两段代码均出姿App.vue

添加过渡效果

在App.vue中添加下列代码

<style>

.moveUp-enter-active {

animation: fadeIn 1s ease-in;

}

@keyframes fadeIn {

0% { opacity: 0; }

50% { opacity: 0.5; }

100% { opacity: 1; }

}

.moveUp-leave-active {

animation: moveUp 0.3s ease-in;

}

@keyframes moveUp {

0% { transform: translateY(0); }

100% { transform: translateY(-400px); }

}

</style>

Target 放在同一个div中

此时的DestinationShow页面根目录下是两个Section标签,需要将这两个标签放在同一个根目录下(div即可)

至此,动画效果已经实现

移出效果

App.vue 中修改 router-view

<router-view v-slot="{Component}">code>

<transition name="slide" mode="out-in">code>

<component :is="Component" :key="$route.path"></component>code>

</transition>

</router-view>

App.vue 中添加CSS过渡效果

<style>

.slide-enter-active,

.slide-leave-active {

transition: opacity 1s,transform 1s;

}

.slide-enter-from,

.slide-leave-to {

opacity: 0;

transform: translateX(-30%);

}

</style>

另外记得将Target放在同一个Div下,动画效果即可实现。

第二十集 | 404 Not Found 页面

创建views/NotFound.vue页面

<template>

<div>

<h1>Not Found</h1>

<p>

Oops,we cloudn't find this page.Try going

<router-link to="/">home</router-link>code>

</p>

</div>

</template>

增加路由

router/index.js中添加以下代码即可。

{

path: '/:pathMatch(.*)*',

name: 'NotFound',

component: () => import('@/views/NotFound.vue')

}

第二十一集 | 404页面优化

在上一集主要解决的问题是访问不存在的URL时会出现404页面,但是DestinationShow页面的路径含有两个不确定参数,那么我们使用/destination/1000/abc访问页面,它返回的内容也是一个空白页面,如何让DestinationShow页面也能判断是否404呢?

我们可以对Destination做如下修改,当判断data.json中没有找到我们当前访问的ID时,就返回NotFoundpage

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue'),

props: route=> ({ ...route.params,id: parseInt(route.params.id)}),

beforeEnter(to,from) {

const exists = sourceData.destinations.find(

destination => destination.id === parseInt(to.params.id)

)

if(!exists) return { name: 'NotFound'}

},

children: [

{

path: ':experienceSlug',

name: 'experience.show',

component: () => import('@/views/ExperienceShow.vue'),

props: route => ({ ...route.params,id: parseInt(route.params.id)})

}

]

},

这样一来的话,当我们访问不存在的DestinationShow页面的时候,最终会进入到404页面,但是浏览器地址栏的路径也变了(变成了/),我们稍作修改,让地址不发生改变。

{

path: '/destination/:id/:slug',

name: 'destaination.show',

component: import('@/views/DestinationShow.vue'),

props: route=> ({ ...route.params,id: parseInt(route.params.id)}),

beforeEnter(to,from) {

const exists = sourceData.destinations.find(

destination => destination.id === parseInt(to.params.id)

)

if(!exists) return {

name: 'NotFound',

params: { pathMatch: to.path.split('/').slice(1)},

query: to.query,

hash: to.hash

}

},

children: [

{

path: ':experienceSlug',

name: 'experience.show',

component: () => import('@/views/ExperienceShow.vue'),

props: route => ({ ...route.params,id: parseInt(route.params.id)})

}

]

},

其中,to的结构如下:

{

"fullPath":"/destination/123123/brazil",

"path":"/destination/123123/brazil",

"query":{ },

"hash":"",

"name":"destaination.show",

"params": {

"id":"123123",

"slug":"brazil"

},

"matched":[

{

"path":"/destination/:id/:slug",

"name":"destaination.show",

"meta":{ },

"props":{ },

"children":[

{

"path":":experienceSlug",

"name":"experience.show"

}

],

"instances":{ },

"leaveGuards":{ },

"updateGuards":{ },

"enterCallbacks":{ },

"components":{

"default":{ }

}

}

],

"meta":{ },

"href":"/destination/123123/brazil"

}

from的结构如下:

{

"path":"/",

"params":{ },

"query":{ },

"hash":"",

"fullPath":"/",

"matched":[],

"meta":{ }

}

第二十二集 | 页面切换时回到顶部

当我们在DestinationShow页面将页面拉到最底下的时候,我们点击另一个DestinationShow页面,会发现我们还是在页面的最底部(尽管页面已经切换了),这是因为我们使用的是同一个页面,改变的仅仅是页面内容。我们可以使用router提供的scrollBehavior功能,将页面回滚至顶部,代码如下:

const router = createRouter({

history: createWebHistory(),

// 通过添加记录的方式使用router

routes,

scrollBehavior(to,from,savedPosition) {

// return {top: null,left: null, behavior: null}

return savedPosition || new Promise((resolve) => {

setTimeout(() => resolve({ top:0,behavior: 'smooth'}),1000)

})

}

})

这样一来,当我们点击页面的时候,便会执行scrollBehavior,如果它是空的话,将会返回{top:0},让页面回到顶部,同时behavior可以控制方式,这里采用的smooth可以让页面平滑滚动至顶部。

第二十三集 | 受保护的页面(登录才能使用)

在一些情况下,我们需要对一些页面做权限校验,在Router4中,我们只需要在路由中的原属性(meta property)添加一些信息就可以让该页面需要验证。随后我们使用router.beforeEach来在跳转之前做校验,为了方便演示,我们先在代码中创建两个页面。

Protected 页面

views/Protected.vue

<template>

<h1>Hello,{ {username}}.</h1>

<button @click="logout">Logout</button>code>

</template>

<script>

export default {

data() {

return {

username: window.user

}

},

methods: {

logout() {

window.user = null;

this.$router.push({name: 'Home'})

}

}

}

</script>

Login 页面

views/Login.vue

<template>

<div class="login">code>

<form class="form" @submit.prevent="login">code>

<h1>Login</h1>

<label for="username">Username</label>code>

<input v-model="username" name="username" type="text" class="input">code>

<label for="password">Password</label>code>

<input v-model="password" name="password" type="text" class="input">code>

<button class="btn">Login</button>code>

</form>

</div>

</template>

<script>

export default {

data() {

return {

username: '',

password: ''

}

},

methods: {

login() {

window.user = this.username

this.$router.push({name: 'protected'})

}

}

}

</script>

路由信息

我们先创建两个页面的路由

{

path: '/login',

name: 'login',

component: () => import('@/views/Login.vue')

},

{

path: '/protected',

name: 'protected',

component: () => import('@/views/Protected.vue'),

meta: {

requiresAuth: true

}

},

可以看到,其中Protected页面有一个meta属性,里面放置了一个键值对,用于一会儿做校验

router.beforeEach

router.beforeEach((to,from) => {

if(to.meta.requiresAuth && !window.user) {

return { name: 'login'}

}

})

这一段代码会在每个页面跳转时都触发一次,如果目标页面的原属性(meta property)中含有requiresAuth且值为false,并且此时window.user中没有值,那么我们就返回一个名为login路由(也就是跳转到登录页面)。

我们在导航栏添加上Protected的link:

<router-link :to="{name: 'protected'}">Protected</router-link>code>

测试效果:当我们在导航栏点击Protected,会进入到登录页面,此时输入用户名与密码之后,点击登录,才会进入到Protected页面。在Protected页面点击Logout会退出并回到主页面。

第二十四集 | 重定向

需求说明

我们目前的登录页面写死跳转到Protected页面,但是在一些情况下,比如你的朋友在’养猪场管理系统’分享了’母猪的产后护理手册’这个页面的链接给你,而你在浏览器打开页面后提示需要登录,这时候登录后却跳转到了Home页面(参考这边的Protected页面),这是不合理的,用户需要的是点进一个连接后,虽然跳转至登录页面,但是登陆之后会继续前往那个页面。

代码示例:在项目中我们定义一个需要认证的Invoices.vue页面,这样一来我们就有了两个需要登录的页面我们想要实现这样的效果:如果我们没有指定我们想要进入的页面(直接访问登录页面并登录)则进入Protected页面,如果我们没登录点进了某个需要登录的页面,则跳转到登录页面,登录完成后进入到目标页面。仔细思考一下,这样一来,Protected页面就成了登录后的默认页面,但是不是唯一选择,如果我们的地址是/invoices,那么我们会先进行登录,登录完成后直接进入invoices页面

代码变动 1 | 添加Invoices页面

views/Invoices.vue

<template>

<div>

<h1>Invoices</h1>

</div>

</template>

代码变动 2 | Login页面代码变动

我们将views/Login.vue中的login方法做如下修改

我们从query参数中获取一个redirect的值,如果该值不是空,则赋值给redirectPath,如果是空,则将/protected赋值给redirectPath登录成功后,直接路由到redirectPath地址

login() {

window.user = this.username

const redirectPath = this.$route.query.redirect || '/protected'

this.$router.push(redirectPath)

}

代码变动 3 | router/index.js

router.beforeEach((to,from) => {

if(to.meta.requiresAuth && !window.user) {

return { name: 'login',query: { redirect: to.fullPath}}

}

})

上述代码中,我们首先通过router.beforeEach来判断页面是否需要权限,如果需要的话会返回login页面的路由,并且加上query地址参数(Target页面的全地址),这样一来,如果我们访问的页面是Invoices页面,那么地址栏将会变成http://localhost:5173/login?redirect=/invoices,同理,Protected页面也会发生变化,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如此一来,当我们登陆之后,Login页面的登录方法就会将我们的目标页面的路由进行<code>route.push,我们便前往了我们的目标页面

第二十五集 | 自定义AppLink

某些情况下,如果我们的导航栏链接是动态的,比如从数据库加载或其它方式,此时若是我们有外部链接需求,单单的router-link便无法满足我们的需求,我们需要自己定义一个组件来替代router-link

AppLink

创建component/AppLink.vue

代码 说明
<code>_blank 在新标签中打开
<code>ref="noopener"code> 见下文
<code><slot/> 插槽:外部AppLink标签中的内容将再次显示

rel=”noreferrer” 标签是一个特殊的 HTML 属性,可以添加到链接标签 <code><a>。它通过从 HTTP header 中删除 referrer information(引荐信息)来防止将referral info(引荐来源信息)传递到目标网站。 这意味着在 Google 分析中,来自具有 rel=”noreferrer” 属性的链接的流量将显示为Direct Traffic(直接流量)而不是Referral(推荐流量)。

相关资料

<template>

<a v-if="isExternal" target="_blank" ref="noopener" class="external-link" :href="to"><slot/></a>code>

<router-link v-bind="$props" v-else class="internal-link"><slot/></router-link>code>

</template>

<script>

import {RouterLink} from 'vue-router'

export default {

props: {

...RouterLink.props

},

computed: {

isExternal() {

return typeof this.to === 'string' && this.to.startsWith('http')

}

}

}

</script>

全局注册

import AppLink from "@/components/AppLink.vue";

createApp(App)

.use(router)

.component('AppLink',AppLink)

.mount('#app')

使用

TheNavigation.vue中的router-link替换为AppLink

<template>

<div id="nav">code>

<AppLink id="logo" to="/">Vue School Travel APP</AppLink>code>

<AppLink

v-for="destaination in destinations"code>

:key="destaination.id"code>

:to="{name: 'destaination.show',params: {id: destaination.id, slug: destaination.slug}}">code>

{ { destaination.name }}

</AppLink>

<AppLink :to="{name: 'protected'}">Protected</AppLink>code>

<AppLink to="http://vueschool.io">Vue School</AppLink>code>

</div>

</template>

代码说明

我们定义组件并在main.js中注册之后,便可以在全局使用该组件。当我们在外部使用AppLink组件时,AppLink标签中的内容将会传递到组件内部,并且以插槽<slot/>的形式出现。我们在<AppLink to="http://vueschool.io">Vue School</AppLink>code>中的to传入的属性,可以在标签内以参数的形式使用,因此我们定义了计算属性来判断该参数的类型与值的开头,以判断是内部标签还是外部标签。如果是外部标签,我们将to放入a标签中,并且用v-if来判断是否显示改标签(是外部标签则显示)如果是内部标签,那它肯定不是一个String,也不是以http开头,此时我们用v-else显示router-link

疑惑

为什么有的地方用to,有的地方用$props为什么to没有这个参数却可以使用?为什么外部的router-link中传入的是to/但是内部用to却不行呢?

第二十六集 | 使用组合式 API

使用组合式API(setup)实现登录

Login.vue 中做如下修改

export default {

setup() {

const username = ref('')

const password = ref('')

const router = useRouter()

const route = useRoute()

const login = () => {

window.user = username.value

const redirectPath = route.query.redirect || '/protected'

router.push(redirectPath)

}

return { username,password,login}

}

}

以上代码中我们通过ref来获取输入框信息,这边若是变量名与输入框中的value值一致则不用在ref中传入实际参数,否则需要传入参数

我们自定义了login方法,并且通过username.value来获取输入的值,随后判断等

使用组合式API实现跳转页面前确认

我们在CSDN也经常会遇到这种功能:当我们在编辑博客的时候关闭页面,会跳出来一个弹框提示你是否要离开,离开后内容会消失。这种功能我们可以使用vue-routeronBeforeRouteLeave来实现。

<script>

import {onBeforeRouteLeave} from "vue-router";

export default {

setup() {

onBeforeRouteLeave((to,from) => {

const answer = window.confirm(

'Are you sure you want to leave? Invoices are super awesome!'

)

if(!answer) return false

})

}

}

</script>

当我们要离开页面的时候,会提示Are you sure you want to leave? Invoices are super awesome!,此时点取消则不会离开页面,点确认则离开页面。

onBeforeRouteLeavereturn false则不会执行跳转操作,否则执行跳转操作。

额外知识 1 (Part5) | $router.push 与 replace

$router 是一个栈,如果我们使用push的话是进行压栈操作,新的路由被一个一个压入栈中,所以我们在浏览器中点击返回按钮的时候,是进行了一个出栈操作,将栈最顶部的页面弹出,此时显示的就是上一级页面。

如果我们使用replace的话,就是替换栈最顶部的内容,此时是新页面替换旧页面,所以旧页面的“历史记录”将不存在,也就不能再次返回到上一级页面。

这种操作很常见,比如网站登录后,用户返回将无法返回到“登录页面”。

额外知识 2 (Part5) | 多个子组件实现特定页面显示组件

由于目前还没有一个特定的导航栏来自“受保护页面”来回切换,我们希望加上一个导航栏,允许我们在受保护页面之间切换(Protected/Invoices),该页面在普通页面不显示,只在受保护页面显示。

实现方案一:我们可以定义一个组件,然后在两个受保护页面分别导入并使用该组件。(这也是一个实现思路)

实现方案二:我们定义一个组件,然后在App.vue页面使用该组件的名称导入该组件,随后将该组件注册为受保护页面的子组件,代码如下:

新增加的导航组件代码

components/LeftSidebar.vue

<template>

<ul>

<li><router-link :to="{name: 'protected'}">Protected</router-link></li>code>

<li><router-link :to="{name: 'invoices'}">Invoices</router-link></li>code>

</ul>

</template>

<style scoped>

ul {

background: white;

padding: 10px;

list-style: none;

margin-right: 20px;

}

li {

margin: 10px;

}

</style>

在路由中注册该组件

我们在router/index.js中,导入这两个组件,并将组件注册为受保护页面的子组件

{

path: '/protected',

name: 'protected',

components: {

default: () => import('@/views/Protected.vue'),

LeftSidebar: () => import('@/components/LeftSidebar.vue')

},

meta: {

requiresAuth: true

}

},

{

path: '/invoices',

name: 'invoices',

components: {

default: () => import('@/views/Invoices.vue'),

LeftSidebar: () => import('@/components/LeftSidebar.vue')

},

meta: {

requiresAuth: true

}

},

这样一来,我们便拥有了一个组件,且两个受保护页面都将该组件注册为了自己拥有的子组件,然后,我们在App.vue中使用该组件。

<template>

<TheNavigation/>

<br>

<div class="container">code>

<!-- 这一行是我们新增加的组件,使用name调用该组件 -->

<router-view class="view left-sidebar" name="LeftSidebar"></router-view>code>

<router-view v-slot="{Component}" class="main-view">code>

<transition name="fade" mode="out-in">code>

<component :is="Component" :key="$route.path"></component>code>

</transition>

</router-view>

</div>

</template>

<script>

import TheNavigation from '@/components/TheNavigation.vue';

export default {

components: {

TheNavigation

}

}

</script>

<style scoped>

.fade-enter-active,

.fade-leave-active {

transition: opacity 0.3s;

}

.fade-enter,

.fade-leave-to {

opacity: 0;

}

.container {

display: flex;

}

.left-sidebar {

width: 20%;

}

.main-view {

width: 100%;

}

</style>

此时当main-view被加载出来的时候,我们的页面中便拥有了一个叫LeftSidebar的组件,该组件就会被显示到上面的router-view中去。若是进入普通页面,则页面中没有该组件,那么该组件就无法被显示。

补充知识 3 (Part 5) | 地址重定向 与 重命名

现在我们项目的根路径是’/‘,但是有一些人的使用习惯是’/home’,若是用户在菜单栏直接输入’/home’,似乎返回一个404页面又不是很友好,这个页面明明存在,只是叫法不同,因此我们可以使用重定向,当用户访问/home时,跳转到/页面上。

重定向与重命名的区别在于:

实现方式不同,重定向需要新建一个路由地址,并且使用redirect将新的地址指向旧地址;重命名只需要在旧地址上加上alias重定向会改变我们输入的地址值,重命名不会,比如我们用’/home’重定向到’/‘,地址栏出现的就是’/‘,而重命名出现的则是’/home’

重命名 | 使用alias重命名

{ path: '/',name: 'Home',component: Home,alias: '/home'},

重定向 1 | 直接引入路径

{ path: '/home',redirect: '/'},

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重定向 2 | 用页面名称重定向到某个页面

<code>{ path: '/home',redirect: { name: 'Home'}},

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重定向 3 | 使用一个方法

下面两段代码均采用方法的方式,下面一种会比较复杂一些,我们可以进行一些操作逻辑之类的。

<code>{ path: '/home',redirect: to => '/'},

{

path: '/home',redirect: to => () => {

return '/'

}

},

同时定义多个重定向与重命名

注意:重定向与重命名都可以定义多个,重定向需要定义多组{}标签,重命名需要在[]中写入多个名称,如下所示:

{ path: '/',name: 'Home',component: Home,alias: ['/r1','/r2','/r3']},

{ path: '/a',redirect: '/'},

{ path: '/b',redirect: { name: 'Home'}},

{ path: '/c',redirect: to => '/'},

{

path: '/d',redirect: to => () => {

return '/'

}

},

如此一来,我们访问a\b\c都将会重定向到’/‘,访问r1\r2\r3地址栏显示的依旧是r1\r2\r3,但是内容显示的时’/’

当重定向与重命名冲突

当重定向的名称与重命名的名称冲突的时候,将会采取“就近原则”,写在上面的内容会覆盖下面的内容,简而言之:越往上优先级越高,才生效。

补充知识 4 (Part5) | 导航失效

导航失效的场景:

用户已经在该页面,却仍然点击了该页面的导航受保护的页面,返回false我们进不去我们在上一个导航的动作还没完成的时候(可以理解成进入页面),就点击了下一个导航重定向到一个新的地址导航报错

错误类型 说明
<code>NavigationFailureType.duplicated nav was prevented because we’re already on the requested route
<code>NavigationFailureType.aborted false was returned inside of a navifation guard to the navigation
<code>NavigationFailureType.cancelled a new navigation took place before the current navigation cloud finish

我们在Home页面定义一个按钮,点击按钮跳转到Home页面,(注意是Home -> Home),因此这肯定是一个导航重复错误。

<code>async triggerRouterError() {

// 如果跳转正常则返回 false/null等???

const navigationFailure = await this.$router.push('/')

// 如果报错类型是 重复点击页面的话,执行下面的代码,否则执行else

if(isNavigationFailure(navigationFailure,NavigationFailureType.duplicated)) {

console.log(JSON.stringify(navigationFailure));

/**

* 一个完整的报错结果如下所示:

*

* {"type":16,"to":{"fullPath":"/","path":"/","query":{},"hash":"","name":"Home","params":{},

* "matched":[{"path":"/","name":"Home","meta":{},"props":{"default":false},"children":[],

* "instances":{"default":{"destinations":[{"name":"Brazil","slug":"brazil","image":"brazil.jpg",

* "id":1,"description":"all about Brazil, suspendisse lobortis pharetra tempor. Cras eleifend

* ante sed arcu interdum, in bibendum enim ultricies. Integer rutrum quis risus at tempor.

* Maecenas facilisis, nisi vel pellentesque maximus, lectus felis malesuada purus, a pulvinar

* elit est quis turpis. Duis convallis purus quis finibus consequat. Pellentesque faucibus

* tincidunt augue non consequat. Donec fringilla at est sit amet blandit. Nunc at porttitor

* ligula. Fusce sed odio turpis. Suspendisse lobortis pharetra tempor. Cras eleifend ante

* sed arcu interdum, in bibendum enim ultricies. Integer rutrum quis risus at tempor. Maecenas

* facilisis, nisi vel pellentesque maximus, lectus felis malesuada purus, a pulvinar elit est

* quis turpis.","experiences":[{"name":"Iguaçu Falls","slug":"iguacu-falls","image":"iguacu-falls.jpg",

* "description":"Suspendisse lobortis pharetra tempor. Cras eleifend ante sed arcu interdum,

* in bibendum enim ultricies. Integer rutrum quis risus at tempor. Maecenas facilisis, nisi vel

* pellentesque maximus, lectus felis malesuada purus, a pulvinar elit est quis turpis. Duis

* convallis purus quis finibus consequat. Pellentesque faucibus tincidunt augue non consequat.

* Donec fringilla at est sit amet blandit. Nunc at porttitor ligula. Fusce sed odio turpis.

* Suspendisse lobortis pharetra tempor. Cras eleifend ante sed arcu interdum, in bibendum enim ultricies.

* Integer rutrum quis risus at tempor. Maecenas facilisis, nisi vel pellentesque maximus,

* lectus felis malesuada purus, a pulvinar elit est quis turpis. Duis convallis purus quis finibus consequat.

* Pellentesque faucibus tincidunt augue non consequat. Donec fringilla at est sit amet blandit.

* Nunc at porttitor ligula. Fusce sed odio turpis."},{"name":"Pão de Açúcar","slug":"pao-de-acucar",

* "image":"pao-de-acucar.jpg","description":"Suspendisse lobortis pharetra tempor. Cras eleifend

* ante sed arcu interdum, in bibendum enim ultricies. Integer rutrum quis risus at tempor. Maecenas

* facilisis, nisi vel pellentesque maximus, lectus felis malesuada purus, a pulvinar elit est quis turpis.

* Duis convallis purus quis finibus consequat. Pellentesque faucibus tincidunt augue non consequat.

* Donec fringilla at est sit amet blandit. Nunc at porttitor ligula. Fusce sed odio turpis.

* Suspendisse lobortis pharetra tempor. Cras eleifend ante sed arcu interdum, in bibendum enim ultricies.

* Integer rutrum quis risus at tempor. Maecenas facilisis, nisi vel pellentesque maximus, lectus felis

* malesuada purus, a pulvinar elit est quis turpis. Duis convallis purus quis finibus consequat.

* Pellentesque faucibus tincidunt augue non consequat. Donec fringilla at est sit amet blandit.

* Nunc at porttitor ligula. Fusce sed odio turpis."},{"name":"Sao Paulo","slug":"sao-paulo",

* "image":"sao-paulo.jpg","description":"Suspendisse lobortis pharetra tempor. Cras eleifend ante sed arcu

* interdum, in bibendum enim ultricies. Integer rutrum quis risus at tempor. Maecenas facilisis, nisi vel

* pellentesque maximus, lectus felis malesuada purus, a pulvinar elit est quis turpis. Duis convallis purus

* quis finibus consequat. Pellentesque faucibus tincidunt augue non consequat. Donec fringilla at est sit

* amet blandit. Nunc at porttitor ligula. Fusce sed odio turpis. Suspendisse lobortis pharetra tempor.

* Cras eleifend ante sed arcu interdum, in bibendum enim ultricies. Integer rutrum quis risus at tempor.

* Maecenas facilisis, nisi vel pellentesque maximus, lectus felis malesuada purus, a pulvinar elit est

* quis turpis. Duis convallis purus quis finibus consequat. Pellentesque faucibus tincidunt augue non consequat.

* Donec fringilla at est sit amet blandit. Nunc at porttitor ligula. Fusce sed odio turpis."},

* {"name":"Salvador","slug":"salvador","image":"salvador.jpg","description":"Suspendisse lobortis pharetra tempor.

* Cras eleifend ante sed arcu interdum, in bibendum enim ultricies. Integer rutrum quis risus at tempor.

* Maecenas facilisis, nisi vel pellentesque maximus, lectus felis malesuada purus, a pulvinar elit est quis turpis.

* Duis convallis purus quis finibus consequat. Pellentesque faucibus tincidunt augue non consequat.

* Donec fringilla at est sit amet blandit. Nunc at porttitor ligula. Fusce sed odio turpis.

* Suspendisse lobortis pharetra tempor. Cras eleifend ante sed arcu interdum, in bibendum enim ultricies.

* Integer rutrum quis risus at tempor. Maecenas facilisis, nisi vel pellentesque maximus, lectus felis malesuada

* purus, a pulvinar elit est quis turpis. Duis convallis purus quis finibus consequat. Pellentesque faucibus

* tincidunt augue non consequat. Donec fringilla at est sit amet blandit. Nunc at porttitor ligula.

* Fusce sed odio turpis."}]},{"name":"Panama","slug":"panama","image":"panama.jpg","id":2,"description":

* "all about panam

*/

} else {

// everything is well

}

}

补充知识 5 (Part5) | 使用正则表达式匹配路由

我们可以使用正则表达式来匹配路由,凡是符合该正则表达式的URL都将被定向到该路由。

所有的采用正则表达式的地址值,都会被当做query参数传入子页面

我们将使用Login页面来实验

示例一 | 使用 + 匹配多个数字

以下代码中我们访问example/123则可以访问到登录页面,但是example/abc不行,只能匹配数字。

以下代码中d+表示可以匹配多个数字,如果只有一个d则表示只能匹配一个数字

{

path: '/example/:id(\\d+)' ,

component: () => import('@/views/Login.vue')

}

示例二 | 使用双 + 匹配多个/数字(重要)

下面表达式中,我们可以匹配多个/数字,如:/example/123/234/345/456可以访问到,但是/example/123/234/abc则访问不到

地址 是否可以访问到 备注
<code>/example × 没有数字
<code>/example/123/234/345 有多个且全是数字
<code>/example/123/234/abc × 有字母
<code>/example/123 有数字

<code>{

path: '/example/:id(\\d+)+' ,

component: () => import('@/views/Login.vue')

},

:id+表示可以有多个地址值

:id(\\d+)+表示这些地址值都必须是数字

实例三 | 使用 * 表示0个或多个

以下代码中,我们访问/example可以访问到,访问/example/123/234也可以访问到

{

path: '/example/:id(\\d+)*' ,

component: () => import('@/views/Login.vue')

},

实例四 | 使用 ? 匹配或一个
地址值 是否可以访问到 备注
<code>/example 可以 符合0个或1个
<code>/example/123 可以 符合0个或1个
<code>/example/1/2/3 不可以 不符合0个或1个

<code>{

path: '/example/:id(\\d+)?' ,

component: () => import('@/views/Login.vue')

},

示例五 | 多个路由匹配

以下代码中,若是我们输入的只有数字,则会匹配名为orders的路由,如果含有其他内容,则匹配名为product的路由

{ path: '/:orderId(\\d+)',name: 'orders'},

{ path: '/:productName',name: 'product'},

补充知识 6 | 动态添加/删除路由

动态添加路由

我们可以使用this.$router.addRoute({})的方式来动态添加路由,添加后的路由我们可以访问,但是刷新后路由会消失,地址无法访问,再次添加后可以访问。

this.$router.addRoute({

name: 'dynamic',

path: '/dynamic',

component: () => import('@/views/Login.vue')

})

动态删除路由

使用this.$router.removeRoute('路由名称')动态删除路由

this.$router.removeRoute('dynamic')

额外的奖励 1 | Router的传参方式

params和query是Vue Router的两种传参方式,其中Params不会出现在地址栏中,而Query会在地址栏中(用?拼接在地址后面)

params可以使用props接收参数,也可以使用this.$route.params来接收参数

query只可以使用this.$route.query来接收参数



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。