一、前言
在上一章的学习中,我们简单介绍了前端路由的概念,以及如何在 Vue 中通过使用 Vue Router 来实现我们的前端路由。但是在实际使用中,我们经常会遇到路由传参、或者一个页面是由多个组件组成的情况。本章,我们就来介绍下在这两种情况下 Vue Router 的使用方法以及一些可能涉及到的概念。
学习系列目录地址:https://www.cnblogs.com/danvic712/p/9549100.html
仓储地址:https://github.com/Lanesra712/VueTrial/tree/master/chapter02-bronze/router/pass
二、干货合集
1、命名路由
在某些时候,我们期望生成的路由 URL 地址可能会很长,在使用中可能会显得有些不便。这时候通过一个名称来标识一个路会更方便一些,因此在 Vue Router 中,我们可以在创建 Router 实例的时候,通过在 routes 配置中给某个路由设置名称,从而方便的调用路由。
const router = new VueRouter({ routes: [ { path: '/form', name: 'form', component: 'form 组件' } ] })
当我们使用命名路由之后,当需要使用 router-link 标签进行跳转时,就可以采取给 router-link 的 to 属性传一个对象的方式,跳转到指定的路由地址上。
<router-link :to="{ name: 'form'}">Userrouter-link>
2、命名视图
当我们打开一个页面时,整个页面可能是由多个 Vue 组件所构成的,例如,我们的后台管理首页可能是由 sidebar (侧导航) 、header(顶部导航)和 main (主内容)这三个 Vue 组件构成的。此时,当我们通过 Vue Router 构建路由信息时,如果一个 URL 只能对应一个 Vue 组件,整个页面肯定是无法正确显示的。
在上一章的学习中,我们在构建路由信息的时候有使用到两个特殊的标签:router-view 和 router-link。通过 router-view 标签,我们就可以指定组件渲染显示到什么位置。因此,当我们需要在一个页面上显示多个组件的时候,就需要在页面中添加多个的 router-view 标签。
那么,是不是可以通过一个路由对应多个组件,然后按需渲染在不同的 router-view 标签上呢?按照上一章中介绍的关于 Vue Router 的使用方法,我们可以很容易的实现下面的代码。
<div id="app"> <router-view>router-view> <div class="container"> <router-view>router-view> <router-view>router-view> div> div> <template id="sidebar"> <div class="sidebar"> sidebar div> template> <script> // 1、定义路由跳转的组件模板 const header = { template: 'header' } const sidebar = { template: '#sidebar', } const main = { template: 'main' } // 2、定义路由信息 const routes = [{ path: '/', component: header }, { path: '/', component: sidebar }, { path: '/', component: main }] const router = new VueRouter({ routes }) // 3、挂载到当前 Vue 实例上 const vm = new Vue({ el: '#app', data: {}, methods: {}, router: router }); script>
可以看到,并没有实现我们想要实现的效果,当我们将一个路由信息对应到多个组件时,不管有多少个的 router-view 标签,程序都会将第一个组件渲染到所有的 router-view 标签上。
因此,在这种情况下,我们需要实现的是一个路由信息可以按照我们的需要去渲染到页面中指定的 router-view 标签上,而在 Vue Router 中,我们则可以通过命名视图的方式实现我们的需求。
命名视图,从名称上看可能无法阐述的很清楚,与命名路由的实现方式相似,命名视图通过在 router-view 标签上设定 name 属性,之后,在构建路由与组件的对应关系时,以一种 name:component 的形式构造出一个组件对象,从而指明是在哪个 router-view 标签上加载什么组件。
注意,这里在指定路由对应的组件时,使用的是 components(包含 s)属性进行配置组件。实现命名视图的代码如下所示。
<div id="app"> <router-view>router-view> <div class="container"> <router-view name="sidebar">router-view> <router-view name="main">router-view> div> div> <script> // 2、定义路由信息 const routes = [{ path: '/', components: { default: header, sidebar: sidebar, main: main } }] script>
在 router-view 中,默认的 name 属性值为 default,所以这里的 header 组件对应的 router-view 标签就可以不设定 name 属性值。完整的示例代码如下。
<div id="app"> <router-view>router-view> <div class="container"> <router-view name="sidebar">router-view> <router-view name="main">router-view> div> div> <template id="sidebar"> <div class="sidebar"> sidebar div> template> <script> // 1、定义路由跳转的组件模板 const header = { template: 'header' } const sidebar = { template: '#sidebar' } const main = { template: 'main' } // 2、定义路由信息 const routes = [{ path: '/', components: { default: header, sidebar: sidebar, main: main } }] const router = new VueRouter({ routes }) // 3、挂载到当前 Vue 实例上 const vm = new Vue({ el: '#app', data: {}, methods: {}, router: router }); script>
3、路由传参
在很多的情况下,例如表单提交,组件跳转之类的操作,我们需要使用到上一个表单、组件的一些数据,这时我们就需要将需要的参数通过参数传参的方式在路由间进行传递。
例如,在下面的示例中,我们想要实现通过点击 main 组件中的子组件 form 组件上的按钮,将表单的内容传递到 info 子组件中进行显示,功能示意图如下所示。
3.1、query 传参
query 查询参数传参,就是将我们需要的参数以 key=value 的方式放在 url 地址中。例如这里的需求,我们需要在 info 组件中显示上一个页面的数据,所以我们的 info 页面显示的 URL 地址应该为 /info?email=xxx&password=xxx,这里的 email 和 password 参数值则是 form 组件上用户输入的值。之后,我们通过获取这两个参数值即可实现我们的需求。
当我们将实例化的 VueRouter 对象挂载到 Vue 实例后,Vue Router 在我们的 Vue 实例上创建了两个属性对象 this.$router(router 实例) 和 this.$route(当前页面的路由信息)。从下图可以看到,我们可以通过 vm.$route 获取到当前页面的路由信息,而这里的 query 对象则是我们需要的。
可以看到,这时我们就可以直接通过 $route.query.参数名 的方式获取到对应的参数值。同时可以发现,fullPath 属性可以获取到当前页面的地址和 query 查询参数,而 path 属性则只是获取到当前的路由信息。
同时,因为在使用 Vue Router 时已经将 VueRouter 实例挂载到 Vue 实例上,因此就可以直接通过调用 $router.push 方法来导航到另一个页面,所以这里 form 组件中的按钮事件,我们就可以使用这种方式完成路由地址的跳转,完整的代码如下。
<div id="app"> <div class="row"> <div class="col"> <router-view>router-view> div> div> <div class="row"> <div class="col-sm-2 sidebar"> <router-view name="sidebar">router-view> div> <div class="col-sm-10 main"> <router-view name="main">router-view> div> div> div> <template id="sidebar"> <div> <ul> <router-link v-for="(item,index) in menu" :key="index" :to="item.url" tag="li">{ {item.name}} router-link> ul> div> template> <template id="main"> <div> <router-view> router-view> div> template> <template id="form"> <div> <form> <div class="form-group"> <label for="exampleInputEmail1">Email addresslabel> <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" v-model="email"> <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.small> div> <div class="form-group"> <label for="exampleInputPassword1">Passwordlabel> <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" v-model="password"> div> <button type="submit" class="btn btn-primary" @click="submit">Submitbutton> form> div> template> <template id="info"> <div class="card" style="margin-top: 5px;"> <div class="card-header"> 输入的信息 div> <div class="card-body"> <blockquote class="blockquote mb-0"> <p>Email Address:{ { $route.query.email }} p> <p>Password:{ { $route.query.password }}p> <footer class="blockquote-footer">Someone famous in <cite title="Source Title">Source Titlecite> footer> blockquote> div> div> template> <script> // 1、定义路由跳转的组件模板 const header = { template: 'header' } const sidebar = { template: '#sidebar', data() { return { menu: [{ name: 'Form', url: '/form' }, { name: 'Info', url: '/info' }] } }, } const main = { template: '#main' } const form = { template: '#form', data() { return { email: '', password: '' } }, methods: { submit() { this.$router.push({ path: '/info?email=' + this.email + '&password=' + this.password }) } }, } const info = { template: '#info' } // 2、定义路由信息 const routes = [{ path: '/', components: { default: header, sidebar: sidebar, main: main }, children: [{ path: '', redirect: 'form' }, { path: 'form', component: form }, { path: 'info', component: info }] }] const router = new VueRouter({ routes }) // 3、挂载到当前 Vue 实例上 const vm = new Vue({ el: '#app', data: {}, methods: {}, router: router }); script>
3.2、param 传参
与获取 query 参数的方式相同,我们同样可以通过 vm.$route 获取到当前路由信息,从而在 param 对象中通过 $route.params.参数名 的方式获取到通过 param 的方式进行参数传递的值。
不过,与 query 查询参数传参不同的是,在定义路由信息时,我们需要以占位符(:参数名)的方式将需要传递的参数指定到路由地址中,实现代码如下。
const routes = [{ path: '/', components: { default: header, sidebar: sidebar, main: main }, children: [{ path: '', redirect: 'form' }, { path: 'form', name: 'form', component: form }, { path: 'info/:email/:password', name: 'info', component: info }] }]
因为在使用 $route.push 进行路由跳转时,如果提供了 path 属性,则对象中的 params 属性会被忽略,所以这里我们可以采用命名路由的方式进行跳转或者直接将参数值传递到路由 path 路径中。同时,与使用 query 查询参数传递参数不同,这里的参数如果不进行赋值的话,就无法与我们的匹配规则对应,也就无法跳转到指定的路由地址中。
const form = { template: '#form', data() { return { email: '', password: '' } }, methods: { submit() { // 方式1 this.$router.push({ name: 'info', params: { email: this.email, password: this.password } }) // 方式2 this.$router.push({ path: `/info/${ this.email}/${this.password}`, }) } }, }
其余的部分与使用 query 查询参数传参的方式相同,这里我就直接给出实现代码了,实现的示意图如下。
<div id="app"> <div class="row"> <div class="col"> <router-view>router-view> div> div> <div class="row"> <div class="col-sm-2 sidebar"> <router-view name="sidebar">router-view> div> <div class="col-sm-10 main"> <router-view name="main">router-view> div> div> div> <template id="sidebar"> <div> <ul> <router-link v-for="(item,index) in menu" :key="index" :to="{name:item.routeName}" tag="li"> { {item.displayName}} router-link> ul> div> template> <template id="main"> <div> <router-view> router-view> div> template> <template id="form"> <div> <form> <div class="form-group"> <label for="exampleInputEmail1">Email addresslabel> <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" v-model="email"> <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.small> div> <div class="form-group"> <label for="exampleInputPassword1">Passwordlabel> <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" v-model="password"> div> <button type="submit" class="btn btn-primary" @click="submit">Submitbutton> form> div> template> <template id="info"> <div class="card" style="margin-top: 5px;"> <div class="card-header"> 输入的信息 div> <div class="card-body"> <blockquote class="blockquote mb-0"> <p>Email Address:{ { $route.params.email }} p> <p>Password:{ { $route.params.password }}p> <footer class="blockquote-footer">Someone famous in <cite title="Source Title">Source Titlecite> footer> blockquote> div> div> template> <script> // 1、定义路由跳转的组件模板 const header = { template: 'header' } const sidebar = { template: '#sidebar', data() { return { menu: [{ displayName: 'Form', routeName: 'form' }, { displayName: 'Info', routeName: 'info' }] } }, } const main = { template: '#main' } const form = { template: '#form', data() { return { email: '', password: '' } }, methods: { submit() { // 方式1 this.$router.push({ name: 'info', params: { email: this.email, password: this.password } }) // 方式2 // this.$router.push({ // path: `/info/${this.email}/${this.password}`, // }) } }, } const info = { template: '#info' } // 2、定义路由信息 const routes = [{ path: '/', components: { default: header, sidebar: sidebar, main: main }, children: [{ path: '', redirect: 'form' }, { path: 'form', name: 'form', component: form }, { path: 'info/:email/:password', name: 'info', component: info }] }] const router = new VueRouter({ routes }) // 3、挂载到当前 Vue 实例上 const vm = new Vue({ el: '#app', data: {}, methods: {}, router: router }); script>
三、总结
这一章主要是介绍了命名路由,以及如何通过使用命名视图在 Vue Router 中将多个组件对应到同一个路由。同时,针对实际使用中我们经常会遇到的路由传参,我们则可以通过 query 或者是 param 的方式进行参数传递。不过,不知道你有没有注意到,不管是 query 传参还是 param 传参,最终我们都是通过 vm.$route 属性获取到参数信息,这无疑意味着组件和路由耦合到了一块,所有需要获取参数值的地方都需要加载 Vue Router,这其实是很不应该的,因此如何实现组件与路由间的解耦,我将在下一章中进行说明。
四、参考
1、从头开始学习vue-router
2、单页面应用路由的两种实现方式
3、你需要知道的单页面路由实现原理
4、面试官: 你了解前端路由吗?
5、前端路由实现原理(history)