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

VueRouter4学习笔记

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

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



文章目录







第一集

创建项目

## 创建项目
npm create vite@latest vue-school-travel-app -- --template vue
## 进入项目目录 
cd vue-school-travel-app 
## 安装依赖
npm install
## 启动项目
npm run dev



第二集

介绍了项目目录结构

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



第三集


代码
  1. 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')
  1. 创建了两个页面 Home/About 并且注册到main.js的router中(上述代码包含)

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


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



第四集

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




第五集

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




第六集

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



第七集 | 关于 History Mode

我们有两种router模式:

  1. createWebHashHistory
  2. createWebHistory

通过上面的模式,我们可以在单页面应用中模拟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}
]

非懒加载打包后如下:

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


情况二 | 懒加载
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')}
]

打包后内容如下:

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

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




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

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

<template>
  <div id="nav">
    <router-link to="/">Home</router-link>
    <!-- router-link 会拦截浏览器重新加载页面的行为 -->
    <router-link to="/brazil">Brazil</router-link>
    <router-link to="/hawaii">Hawaii</router-link>
    <router-link to="/jamaica">Jamaica</router-link>
    <router-link to="/panama">Panama</router-link>

  </div>
</template>

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

<script>
import TheNavigation from '@/components/TheNavigation.vue';

export default {
  components: {
    TheNavigation
  }
}
</script>

在App.vue最上面使用组件

<TheNavigation/>

效果如下:

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




第十集 | 选中的导航样式

  1. router/index.js中的createRouter中添加了以下内容,让被选中的导航可以改变颜色样式等:
const router = createRouter({
    history: createWebHashHistory(),
    routes,
    // 添加的内容
    linkActiveClass: 'vue-school-active-link'
})
  1. 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">
        <h1>All Destinations</h1>
        <div class="destinations">
          <!-- 注意下面的代码 -->
            <router-link 
                v-for="destaination in destinations"
                :key="destaination.id"
                :to="{name: 'destaination.show',params: {id: destaination.id}}">

                <h2>{{ destaination.name }}</h2>
                <img :src="`/images/${destaination.image}`" :alt="destaination.name"/>
            </router-link>
        </div>
    </div>
</template>

TheNavigation.vue 页面代码

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

<template>
    <div class="home">
        <h1>All Destinations</h1>
        <div class="destinations">
            <router-link 
                v-for="destaination in destinations"
                :key="destaination.id"
                :to="{name: 'destaination.show',params: {id: destaination.id}}">

                <h2>{{ destaination.name }}</h2>
                <img :src="`/images/${destaination.image}`" :alt="destaination.name"/>
            </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>

解释: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值,就像是下面一样:

// 传入值
{
    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">
    <img :src="`/images/${experience.image}`" :alt="experience.name"/>
    <span class="card__text">
      {{experience.name}}
    </span>
  </div>
</template>

<script>
export default {
  props: {
    experience: {type: Object,required: true}
  }
}
</script>
DestinationShow页面
<template>
<section v-if="destination" class="destination">
  <h1>{{ destination.name }}</h1>
  <div class="destination-details">
    <img :src="`/images/${destination.image}`" :alt="destination.name">
    <p>{{destination.description}}</p>
  </div>
</section>
  <!-- 新增的section -->
<section class="experiences">
  <h2>Top Experiences in {{ destination.name }}</h2>
  <div class="cards">
    <router-link
        v-for="experience in destination.experiences"
        :key="experience.slug"
        :to="{name: 'experience.show', params: {experienceSlug: experience.slug}}"
    >
      <ExperienceCard
          :experience="experience"
      />
    </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"/>
    <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">
    <button @click="$router.back()">go back</button>
  </span>
</template>

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

<template>
<section v-if="destination" class="destination">
  <h1>{{ destination.name }}</h1>
  <!-- 使用组件 -->
  <GoBack/>
  <div class="destination-details">
    <img :src="`/images/${destination.image}`" :alt="destination.name">
    <p>{{destination.description}}</p>
  </div>
</section>
<section class="experiences">
  <h2>Top Experiences in {{ destination.name }}</h2>
  <div class="cards">
    <router-link
        v-for="experience in destination.experiences"
        :key="experience.slug"
        :to="{name: 'experience.show', params: {experienceSlug: experience.slug}}"
    >
      <ExperienceCard
          :experience="experience"
      />
    </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页面,但是页面间的跳转效果比较生硬,我们可以在页面跳转的时候加上一些动画,且页面间的元素必须符合以下要求:

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

moveUp 效果

VUE2 | router-view 写法

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

<transition name="moveUp">
  <router-view></router-view>
</transition>
Vue3 | router-view 写法

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

<router-view v-slot="{Component}">
  <transition name="moveUp">
    <component  :is="Component" :key="$route.path"></component>
  </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}">
  <transition name="slide" mode="out-in">
    <component  :is="Component" :key="$route.path"></component>
  </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>
    </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>
</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">
    <form class="form" @submit.prevent="login">
      <h1>Login</h1>
      <label for="username">Username</label>
      <input v-model="username" name="username" type="text" class="input">
      <label for="password">Password</label>
      <input v-model="password" name="password" type="text" class="input">
      <button class="btn">Login</button>
    </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>

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




第二十四集 | 重定向

需求说明

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

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

views/Invoices.vue

<template>
  <div>
    <h1>Invoices</h1>
  </div>
</template>
代码变动 2 | Login页面代码变动

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

  1. 我们从query参数中获取一个redirect的值,如果该值不是空,则赋值给redirectPath,如果是空,则将/protected赋值给redirectPath
  2. 登录成功后,直接路由到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页面的登录方法就会将我们的目标页面的路由进行route.push,我们便前往了我们的目标页面。




第二十五集 | 自定义AppLink

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

AppLink

创建component/AppLink.vue

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

rel=”noreferrer” 标签是一个特殊的 HTML 属性,可以添加到链接标签 <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>
  <router-link v-bind="$props" v-else class="internal-link"><slot/></router-link>
</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">
    <AppLink id="logo" to="/">Vue School Travel APP</AppLink>
    <AppLink
        v-for="destaination in destinations"
        :key="destaination.id"
        :to="{name: 'destaination.show',params: {id: destaination.id, slug: destaination.slug}}">
      {{ destaination.name }}
    </AppLink>
    <AppLink :to="{name: 'protected'}">Protected</AppLink>
    <AppLink to="http://vueschool.io">Vue School</AppLink>
  </div>
</template>
代码说明
  1. 我们定义组件并在main.js中注册之后,便可以在全局使用该组件。
  2. 当我们在外部使用AppLink组件时,AppLink标签中的内容将会传递到组件内部,并且以插槽<slot/>的形式出现。
  3. 我们在<AppLink to="http://vueschool.io">Vue School</AppLink>中的to传入的属性,可以在标签内以参数的形式使用,因此我们定义了计算属性来判断该参数的类型与值的开头,以判断是内部标签还是外部标签。
  4. 如果是外部标签,我们将to放入a标签中,并且用v-if来判断是否显示改标签(是外部标签则显示)
  5. 如果是内部标签,那它肯定不是一个String,也不是以http开头,此时我们用v-else显示router-link
疑惑
  1. 为什么有的地方用to,有的地方用$props
  2. 为什么to没有这个参数却可以使用?
  3. 为什么外部的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>
    <li><router-link :to="{name: 'invoices'}">Invoices</router-link></li>
  </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">
    <!-- 这一行是我们新增加的组件,使用name调用该组件 -->
    <router-view class="view left-sidebar" name="LeftSidebar"></router-view>
    <router-view v-slot="{Component}" class="main-view">
      <transition name="fade" mode="out-in">
        <component  :is="Component" :key="$route.path"></component>
      </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时,跳转到/页面上。

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

  1. 实现方式不同,重定向需要新建一个路由地址,并且使用redirect将新的地址指向旧地址;重命名只需要在旧地址上加上alias
  2. 重定向会改变我们输入的地址值,重命名不会,比如我们用’/home’重定向到’/‘,地址栏出现的就是’/‘,而重命名出现的则是’/home’
重命名 | 使用alias重命名
{path: '/',name: 'Home',component: Home,alias: '/home'},
重定向 1 | 直接引入路径
{path: '/home',redirect: '/'},

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

重定向 2 | 用页面名称重定向到某个页面
{path: '/home',redirect: {name: 'Home'}},

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

重定向 3 | 使用一个方法

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

{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) | 导航失效

导航失效的场景:

  1. 用户已经在该页面,却仍然点击了该页面的导航
  2. 受保护的页面,返回false我们进不去
  3. 我们在上一个导航的动作还没完成的时候(可以理解成进入页面),就点击了下一个导航
  4. 重定向到一个新的地址
  5. 导航报错
错误类型说明
NavigationFailureType.duplicatednav was prevented because we’re already on the requested route
NavigationFailureType.abortedfalse was returned inside of a navifation guard to the navigation
NavigationFailureType.cancelleda new navigation took place before the current navigation cloud finish

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

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则访问不到

地址是否可以访问到备注
/example×没有数字
/example/123/234/345有多个且全是数字
/example/123/234/abc×有字母
/example/123有数字
{
    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')
},
实例四 | 使用 ? 匹配或一个
地址值是否可以访问到备注
/example可以符合0个或1个
/example/123可以符合0个或1个
/example/1/2/3不可以不符合0个或1个
{
    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来接收参数

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值