开篇
源码仓库为:https://gitee.com/wx_142da4773a/vue-router-demo.git
基础篇
起步
可以自己创建项目也可以从我的gitee上拉取代码进行练习~
我采用了vue create 来创建项目并选择了router特性。选择过程会询问以下问题
- vue create vue-router-demo 创建一个vue项目
- 请选择预置Please pick a preset: (Use arrow keys)
- default (babel, eslint)
- Manually select features
此处我们选择Manually select features
- 为项目选择所需特性 Check the features needed for your project: (Press to select, to toggle all, <i> to invert selection 按空格进行选择,a 全选,i反选)
- (*) Babel
- ( ) TypeScript
- ( ) Progressive Web App (PWA) Support
- ( ) Router我们需要选择的
- ( ) Vuex
- ( ) CSS Pre-processors
- (*) Linter / Formatter
- ( ) Unit Testing
- ( ) E2E Testing
- Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n),默认选Y,采用history模式
- Pick a linter / formatter config: (Use arrow keys)
- ESLint with error prevention only
- ESLint + Airbnb config
- ESLint + Standard config 我选择了此项
- ESLint + Prettier
- Pick additional lint features: (Press to select, to toggle all, <i> to invert selection)
- (*) Lint on save 选择了此项,每次保存时都进行校验
- ( ) Lint and fix on commit
- Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
- In dedicated config files 选择了此项,单独文件存放配置
- In package.json
- Save this as a preset for future projects? (y/N) 选择y
- 安装完成后执行yarn run serve启动项目
如果是拉取我的源码进行学习,拉取代码后分别执行yarn install 和yarn run serve即可启动项目查看效果,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKELmjxS-1595501136903)(E67E80C4D17146A38BF15E8F939D204F)]
路由使用的基本步骤
- 配置路由规则并导出路由对象:在src/router/index.js中,可以看到如下代码:
import Vue from 'vue' // 导入vue
import VueRouter from 'vue-router' //导入vue-router 插件
import Index from '../views/Index.vue' //导入index组件
// 1. 将VueRouter作为vue的插件,插件具体应该要如何用在最后会有总结
Vue.use(VueRouter)
// 2. 定义路由规则
const routes = [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/blog/:id/blogSecond/:sid',
name: 'Blog',
component: () => import(/* webpackChunkName: "blog" */ '../views/Blog.vue')
},
{
path: '/photo',
name: 'Photo',
component: () => import(/* webpackChunkName: "photo" */ '../views/Photo.vue')
}
]
// 3. 创建 router 对象
const router = new VueRouter({
routes
})
export default router
- 实例化路由对象:将路由挂载到vue的原型上:在main.js中可以看到如下代码
import Vue from 'vue'
import App from './App.vue'
import router from './router.js'
const vm = new Vue({
// 4. 注册 router 对象
router,
render: h => h(App)
}).$mount('#app')
console.log(vm,'vm')
当上述配置完成后我们可以看到控制台的vm信息,如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mSFSmlwm-1595501136906)(C84E142BEABC462782DB204837868DE2)]
可以看到vue实例上挂载了route和router,其中route主要存放当前路由信息如path、hash、name等,而router是路由对象,具体细节可参考 常见问题总结。
动态路由匹配
所谓动态路由是指页面或组件根据不同的参数展示不同的数据
主要使用场景为:比如在写商品详情页面的时候,页面结构都一样,只是商品id的不同,所以这个时候就可以用动态路由动态来进行参数获取,然后就可以进行网络请求以渲染不同的数据。所以可以得出一个结论就是组件相同但数据不同
动态路由如何配置
const User = {
template: '<div>User</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{
path: '/user/:id/org/:orgId',
component: User
}
]
})
总结来看
- 如何设置单个动态参数
- 设置:可以利用path:’/user/:id’这种形式设置id为动态参数
- 获取参数:我们可以在组件内通过this.$route.params.paramName来获取
- 当我们采用/user/xxx 这种形式访问时都会匹配到此路由规则
- 如何设置多个动态参数
- 设置:{path: ‘/user/:id/org/:orgId’,…}
- 获取参数:可以用this. r o u t e . p a r a m s . i d 和 t h i s . route.params.id和this. route.params.id和this.route.params.orgId获取
- 当我们采用/user/123/org/456 这种形式访问时可以匹配到此路由规则
- 如果我们设置了动态路由但进行路由跳转时并未携带参数会造成路由匹配不成功
如下示例:
//组件
const User = {
template: `<div>
<p>User</p>
</div>`
}
//路由
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{
path: '/user/:id/org/:orgId',
component: User
}
]
})
// 调用
<router-link to="/user/234/org">user</router-link>
通过上述方法调用后正则并没有匹配到路由规则
动态路由的参数变化会是什么样的效果?
我们使用动态路由主要解决的是布局相同但数据不同的情况,如用户详情页。那么当我们的用户id发生变化如/user/8009 变为/user/8010时,组件是先销毁再加载进来还是复用呢?
动态路由的参数变化会导致原来已加载的组件实例被复用,因为两个路由指向同一个组件,比起销毁再创建,复用则显得更加高效。这意味着组件声明周期中的钩子函数不会再次触发。如created等
那我们现在想根据路由参数的变化进行接口请求然后进行数据渲染,应该如何操作呢?
路由变化时进行一些特定响应有两种办法:
- 使用watch监控$route对象变化
const User = {
template: '...',
watch:{
$route(to,from){
//当路由变化时可以进行接口请求等
}
}
}
- 或者使用beforeRouteUpdate 导航守卫
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
//当路由变化时可以进行接口请求等处理
//处理完成后不要忘记next()
}
}
捕获所有路由或404路由
如果想匹配任意路径,可以使用通配符*
- path: ‘*’ 可以匹配所有路径
- path: ‘user-*’,可以匹配所有以user开头的路径
- 当使用通配符时请确保顺序正确,一般带通配符的都放在末尾。
- 我们访问一个路由地址时会安装从上至下的方式进行路由正则匹配,匹配到就结束
- 当我们采用通配符时,会自动向$route.params中增加一个叫pathMatch的参数说明匹配到的部分
// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'
嵌套路由
嵌套路由的使用场景是什么样子?
我司的页面很多都是左侧导航菜单、顶部显示用户信息、机构信息、修改密码、全屏等小工具,中间才是真正的内容部分content,左侧和顶部是不变的,当点击左侧菜单时只有content部分变化。这种页面布局其实就是一个嵌套,那么我们的路由也可以用这种嵌套关系来实现。最外层是一个路由,然后内容区域用占位,到时候会根据路由匹配到的组件对占位区域进行动态替换。
嵌套路由如何配置
- 父路由通过使用children来进行嵌套
- children 是一个数组
- 子组件的位置需要使用进行占位
组件:
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view> //子组件
</div>
`
}
路由:
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
编程式导航
声明式导航与编程式导航
- 声明式导航用在直接渲染到页面,如文字,最终会被解析为<a href="/userInfo">文字</a>
- 而编程式导航用于在js中处理逻辑后需要页面进行跳转
声明式导航使用
- 不带参数
<router-link to='/userInfo'></router-link>
<router-link :to="{path:'/userInfo'}"></router-link>
- 带参数
<router-link to="/userInfo?id=1"></router-link>
<router-link :to="'/url?id=' + a"></router-link>
<router-link :to="{path: '/userInfo', query: {id: 1}}"></router-link>
<router-link :to="{name: 'userInfo', params: {id: 1}}"></router-link>
总结发现,如果带参数时path需要与query配套使用,name需要与params配套使用,如果用path与params则会忽略params
编程式导航-$router.push
想要在js逻辑中切换 URL,则使用 $router.push 方法。这个方法会向 history 栈添加一个新的记录,当用户点击浏览器后退按钮时,则回到之前的 URL。
当你点击 时,会在内部调用$router.push,所以说,点击 等同于调用 $router.push
使用方法同router-link,如下:
- 字符串格式
this.$router.push('/userInfo')
this.$router.push('/userInfo?id=1')
- 对象格式
this.$router.push({path:'/userInfo'})
this.$router.push({name:'userInfo'})
this.$router.push({path: '/userInfo',query:{id:1}})
this.$router.push({name: '/userInfo',params:{id:1}})
总结发现,如果带参数时path必须与query配套使用,name必须与params配套使用,如果用path与params则会忽略params
编程式导航-$router.replace
跟$router.push用法很像,只是它不会向history追加一条记录,而是替换掉当前的记录。
声明式指定使用replace:
编程式指定使用replace:$router.replace()
$router.go(n)
参数是一个整数,表情向前或向后退n步
router.go(1) :在浏览器记录中前进一步,等同于 history.forward()
router.go(-1) 后退一步记录,等同于 history.back()
命名路由
给路由起一个别名,方便记忆
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user', //给路由起了一个别名叫user
component: User
}
]
})
命名路由的使用:
- this.$router.push({name:‘user’,params:{userId:1}})
- 如果使用name需要传参请记得与params配套使用
命名视图
会进行视图占位,当加载到组件内容时进行替换
命名视图的使用场景是什么?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YLCcNADO-1595501136909)(4EFC37887D354C72B9259895026EE704)]
如果我们的页面如上图所示,我们在刚进入此页面的时候可以看做是三个组件一起被加载出来然后渲染成客户所需的页面。那就有一个问题:一个路由下如何加载多个组件?也就是我们说的命名视图
如何设置命名视图?
src\router\index.js中可以如下配置
const routes = [
{
path: '/',
name: 'Index',
components: {
default: Index,
Photo: Photo,
Blog: Blog
}
}
]
app.vue页面中如下使用:
<router-view ></router-view>
<router-view name="Photo"></router-view>
<router-view name="Blog"></router-view>
- 如果是路由地址和组件的映射关系是一对一,则component: 单一组件即可
- 如果是路由地址和组件的映射关系是一对多,即一个路由地址要加载多个组件,可以用components:{key:value,key1:value1} 的形式对每个组件进行命名
- 使用的时候可以使用
- 如果我们有多个视图,其中有的可以匹配有的无法匹配则只关注匹配成功的显示
- 如果没有不指定视图名称,将采用default视图
以上述例子为基础,我们在app.vue中如下使用:
<router-view name="xx"></router-view>
<router-view name="Photo"></router-view>
<router-view name="Blog"></router-view>
则最终只能将后两个组件进行渲染到页面,因为没有名为xx的视图
重定向和别名
重定向
重定向是指当用户访问某一链接时URL将会被替换为重定向的链接
//redirect 可以指向一个path
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
//redirect 可以指向一个path,但path是动态路由
const router = new VueRouter({
routes: [
{ path: '/a:id', redirect: '/b:id' }
]
})
//redirect 可以指向一个命名路由
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
//redirect 可以指向一个命名路由带参数
const router = new VueRouter({
routes: [
{ path: '/a:id', redirect: { name: 'foo',params:{id:id} }}
]
})
//redirect 可以指向一个方法
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
const router = new VueRouter({
routes: [
{ path: '/a:id', redirect: to => {
可以返回的格式如下:
return { path: '/foo', query: {id: id} }
return { name: 'baz', params: {id: id} }
return '/with-params/:id'
return '/bar'
}}
]
})
别名
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。也就是访问path和alias是会加载相同的组件,但地址栏显示path和alisa,不进行地址替换
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
路由组件传参 prop
安装动态路由的方式,如果我们在组件中想要获取参数需要使用
r
o
u
t
e
.
p
a
r
a
m
s
.
i
d
,
但
如
果
有
的
页
面
不
能
使
用
route.params.id,但如果有的页面不能使用
route.params.id,但如果有的页面不能使用route,就造成了页面与$route强耦合
为了解耦,我们可以在路由配置中利用prop进行解耦
- 布尔模式
const router = new VueRouter({
routes: [
{
path: '/blog:id',
component: blog,
props: true //会将动态路由的参数转换名称后传递给组件的props
}
]
})
组件中使用参数应该如下:
<template>
<div>
这是 blog 页面
{{this.id}}
</div>
</template>
<script>
export default {
name: 'blog',
props: ['id']
}
</script>
- 对象模式
const router = new VueRouter({
routes: [
{ path: '/user:id', component: A, props: { id: '222' } }
]
})
<template>
<div>
这是 blog 页面
{{this.id}} // 打印222
</div>
</template>
<script>
export default {
name: 'blog',
props: ['id']
}
</script>
- 函数模式
const router = new VueRouter({
routes: [
{ path: '/user:id', component: A, props:(route) => ({ query: route.query.q }) }
]
})