Vue-Router万万没想到你是这样子的一个东西
我们用了这么久的vue以及其他的vue生态成员后,除了感叹写这个玩意的人真**强大之外,有没有深入的理解过它到底是怎样做得这么强大的,在我的掉头发研究中,我今天给大家分享一下我其中一份研究笔记(其实就是本人的学习笔记,哈哈)
Vue-router作为vue的生态圈成员之一,在我的理解里,它是作为vue的一个插件存在的,所以插件肯定存在一个install方法来进行插件的安装,下面我们现在简单的实现一个属于自己的router插件,下面说一下router插件实现的步骤
Router的实现思路
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Anoos2gM-1583676924366)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200308210731536.png)]
Vue-Router里面有两大核心的组件,router-link和router-view,这两个组件一个是改变路由地址的值,一个是显示的视图,通过link去改变url,然后view通过url显示不同的组件是现在router的主要思路,讲完思路,下面就用代码来实现一个router
跟着我左手右手一个慢动作
-
创建一个router类
这里我们新建一个router.js文件
// 创建一个router的实例 class VueRouter { constructor (options){ // 私有化变量 this.$options = options } }
这里我们创建一个VueRouter的类,为了我们使用new Router()
-
实现router类的挂载
let Vue // 预留一个私自变量存传进来的Vue实例 不直接使用防止造成污染 // 实现插件install方法 注册一个router VueRouter.install = function(_Vue){ // 保存传进来的Vue函数 Vue = _Vue // 通过混合的方式才可以获得Vue实例 Vue.mixin({ beforeCreate () { // 这里options是new Vue({})传入的配置 // 确保Vue的根实例上有router的参数 才挂载router // 如果没有传入router的话 就不会进行挂载 if(this.$options.router){ Vue.prototype.$router = this.$options.router } } }) }
router作为一个插件的存在,必须拥有install方法才可以use,这里我们通过Vue.mixin来获取Vue的实例进行路由的挂载,因为在我们的router里面,是先执行Vue.use的方法再进行new Router配置,如果不通过混入的方式,没法获取Vue的实例进行挂载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWaVd2Gl-1583676924369)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200305225508009.png)]
当我们写到这一步,在文件中引入自己的router,router可以执行并挂载了,但是会发现会有两个报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T18oJyuh-1583676924370)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200305225631156.png)]
因为我们的router里面,并没有router-view和router-link两个组件,接下来我们去创建它们
-
创建router类的子组件
实现一个router-link的组件
其实router-link就是一个a标签,使用render函数创建一个dom对象,接受一个to的参数为路由的导航,
export default { props: { to: { type: String, required: true } }, render(h) { return h('a', { attrs: { // 配置url href: '#' + this.to } }, [ // 这里为子节点为任何中间的东西 this.$slots.default ]) } }
实现一个router-view组件
router-view组件讲到底,功能就是根据不同的路由显示不同的组件,跟一些tabs类似
这里我们在实现router-view之前,要想办法去监听当先url的变化,来返回不同的组件,我们修改一下VueRouter类,在里面添加上url的监听事件
// 创建一个router的实例 class VueRouter { constructor(options) { // 利用Vue提供给我们的响应式方法,来监听当前路由地址 // 初始化路由 // 定义响应式的current // 初始话路由的值 利用浏览器api获取当前的路由 const initial = window.location.hash.slice(1) || '/' Vue.util.defineReactive(this, 'current', initial) // 监听hashchange事件 // 避免用户刷新路由的初始化 window.addEventListener('hashchange', this.onHashChange.bind(this)) window.addEventListener('load', this.onHashChange.bind(this)) // 私有化变量 this.$options = options } // 路由变化函数 onHashChange(){ this.current = window.location.hash.slcie(1) } }
在监听url的时候,我们用到里Vue提供给我的响应式方法,这就是Vue-router和Vue的存在关系,Vue-Router只能在Vue中才能发挥作用。我们已经在VueRouter类里面监听了路由的变化了,我们就可以在router-view组件里面根据不同的url来返回不同的组件了
创建一个router-view.js文件
export default { render(h){ // 初始化一个组件变量 let component = null // 通过遍历用户传进来options配置里面的routes信息,我们可以过去到用户的路由表 this.$router.$options.routes.forEach(route=>{ // 如果当前的监听到的url等于用户表里的url,就返回用户配置的组件 if(route.path === this.$router.current){ component = route.component } }) return h(component) } }
在router-view组件里面,我们用过遍历路由表的方法,来匹配路由的组件,这样做我们就可以在路由变化的时候,返回一个正确的组件(这样子的遍历性能偏低,因为每一次路由的变化都要重新遍历一次路由表,后面我们会改进)
然后我们在刚刚VueRouter的install方法里面对两个组件进行注册
// 实现插件install方法 注册一个router VueRouter.install = function (_Vue) { // 保存构造函数 Vue = _Vue // 通过混合的方式获得实例 Vue.mixin({ beforeCreate() { // 这里options是new Vue({})传入的配置 // 确保Vue的根实例上有router的参数 才挂载router // 如果没有传入router的话 就不会进行挂载 if (this.$options.router) { Vue.prototype.$router = this.$options.router } } }) // 引入它们 并注册 Vue.component('router-link', routerLink) Vue.component('router-view', routerView) }
然后你会发现,你的页面已经没有报错了,并且可以正常的使用自己编写的router了,而且很完美,是不是很棒
优化我们自己的router类
在刚刚实现router-view组件的时候,我们匹配组件的方式是通过不断的遍历,这样子在少路由的情况下没什么问题,但在路由多的情况下,这样子会造成不必要的性能损失,那我们怎么处理这个问题呢,这里我们可以先提前的处理好我们的匹配方式
-
提前处理路由表
class VueRouter { constructor(options) { // 在初始化类的时候,缓存好path和route的对应关系 this.routeMap = {} this.$options.routes.forEach(route=>{ this.routeMap[route.path] = route }) } }
在router类创建的时候初始化一个对应关系
然后在router-view组件里直接通过对应关系直接拿取组件
export default { render(h){ // 获取对应表和当前路由 const {routeMap, current} = this.$router // 获取对应组件 const component = routeMap[current].component return h(component) } }
这样子我们就不用每次都去遍历路由表来获取组件了
致命问题
上面的router类,如果技术高超的人,就会发现一个问题,这样子的router,如果一有嵌套路由,不就全完蛋了吗,接下来,我来解决一下嵌到路由的问题。
https://github.com/vuejs/vue-router/blob/dev/src/components/view.js
通过官方的router.js源码,我们可以看到官方的实现方法是,给router-view组件添加深度标签,然后遍历,我这里依照它的思路,勉强的去解决一下嵌套路由,当然肯定没有官方这么标准
- 实现的思路
- 给router-view组件打上一个识别标签,证明是router-view
- 在用遍历来确定嵌套路由的深度
- 通过一个匹配方法来形成一个路由的数组,由0开始输入路由组件
- 监控url变化后重新匹配
修改一下router-view组件
export default {
render(h){
// 添加深度组件标志
// 这里可以通过在VueRouter的实例对象上添加一个属性
this.$vnode.data.routerView = true
// 初始化当前深度为0
let depth = 0
// 获取当前组件的父组件
let parent = this.$parent
// 如果有父组件 我们就遍历
// 通过循环得到最终路由的深度
while(parent){
// 父组件是否存在data
const vData = parent.$vnode && parent.$vnode.data
// 若存在
if(vData){
// 判断深度组件标签是否存在
if(vData.routerView){
// 存在即证明是一层嵌套 深度加1
depth++
}
}
parent = parent.$parent
}
}
然后在router类里定义match方法和matched数组
class VueRouter {
constructor(options) {
// 私有化变量
this.$options = options
// 利用Vue提供给我们的响应式方法,来监听当前路由地址
// 初始化路由
// 定义响应式的current
// 初始话路由的值 利用浏览器api获取当前的路由
this.current = window.location.hash.slice(1) || '/'
// 定义一个路由匹配数组
Vue.util.defineReactive(this, 'matched', [])
this.match()
// 监听hashchange事件
// 避免用户刷新路由的初始化
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
}
// 每次路由变化的函数
onHashChange(){
this.current = window.location.hash.slice(1)
// 清空路由数组,重新遍历
this.matched = []
this.match()
}
// 路由遍历,形成路由数组
match(routes){
routes = routes || this.$options.routes
for(const route of routes ){
// 若为首页
if(route.path === '/' && this.current === '/'){
this.matched.push(route)
return
}
// 例如home/aaa home是包含在home/aaa 中
if(route.path !== '/' && this.current.indexOf(route.path) != -1){
this.matched.push(route)
// 判断是否为子路由
if(route.children){
// 有子路由继续递归
this.match(route.children)
}
return
}
}
}
}
在修改一下router-view组件
let component = null
const route = this.$router.matched[depth]
if(route){
component = route.component
}
return component
修改后的router类,通过match方法递归遍历routes配置,得到matched数组,matched数组是当前路由所形成的数组,例如home/aaa
形成的数组就是[home, aaa]
, 这样子router-view组件可以通过matched数组以及当前的深度来当前路由的组件,就能实现嵌套路由了
最后
亲爱的读者们,以上就是我学习研究vue-router之后想要给大家分享的知识,希望能帮助到各位想要学习的人,本人也是在前端学习的道路上程序员,天下程序员是一家,希望大家一起进步,觉得这篇帮助到你的可以给个👍哦