跟着来,你也可以手写VueRouter - 掘金 (juejin.cn)
这次是参考这位大佬的文章,尝试跟着写。文章很长,慢慢啃。 我个人是比较喜欢这种循序渐进式的教学文章的,一步步的就像升级打怪,不过如果碰到难题基本上只能自己解决了。
框架版本为vue2,vue-router-3.5.2
主体框架搭建
Vue2
vue create hello-vue-router
- 选择vue-router
yarn serve
- 运行
新建文件夹hello-vue-router
- 在src目录下新建hello-vue-router文件夹用来存放手写的vue-router
- 文件夹下新建index.js
更改router/index
- 在src/router/index.js,更改VueRouter引入路径为
// import VueRouter from 'vue-router'// 原路径
import VueRouter from '@/hello-vue-router/index' // 新路径
- 先实现hash模式路由,更改mode为hash
const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})
手写Vue-router
index.js
- 新建一个类 VueRouter 并暴露
- 根据Vue.use()原理,若传入参数是一个对象,Vue.use()会执行参数的install方法,所以需要给VueRouter类添加一个install方法
- 新建install文件引入
/*
* @path:src/hello-vue-router/index.js
* @Description: 入口文件 VueRouter类
*/
import { install } from "./install"
export default class VueRouter {
constructor(options) {
}
}
VueRouter.install = install
注: 原文写的是class VueRouter(){},应该不加(),正确写法如上
install.js
来到本文第一个难点,install。
- Vue.use() 在调用install方法时会将Vue构造函数作为第一个参数,传入install(将传入Vue.use()的arguments第一个参数替换成Vue构造函数)
- install接受Vue构造函数,并将其暴露(因为index的VueRouter类会用到Vue构造函数的api,这样可以减小打包体积)
具体操作步骤
- 检查是否已经注册过并已拿到Vue构造函数,是则不执行后续操作,避免重复注册
- 进行全局Vue混入
mixin
,每个实例都会被影响 - 因为只能在根组件的
this.$options
里拿到router实例,所以需要在$options
构建好的生命周期进行混入,这个生命周期最早就是beforeCreate
Vue.mixin({
// Vue创建前的钩子函数,此时$options已挂载完成
beforeCreate() {
// 通过判断组件实例this.$options有无router属性来判断是否为根实例
// 只有根实例初始化时我们挂载了VueRouter实例router(main.js中New Vue({router})时
if (this.$options.router) {
this._routerRoot = this
// 在 Vue 根实例添加_router属性(VueRouter 实例)
this._router = this.$options.router
this._route = {}
} else {
// 为每个组件实例定义_routerRoot,回溯查找_routerRoot
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
}
})
- 判断是否在根组件中,如果
this.$options.router
存在就表示在根组件中(因为是在main.js中引入的) - 若是根组件就添加
_routerRoot
属性,这个属性就是VueRoueter
实例 - 若不是根组件,则向父元素查找
_routerRoot
属性,若找不到就令其等于this
(暂不明确为什么等于this,可能是防止报错?) - 全局代理Vue的
$router
和$route
属性,让其返回VueRouter
实例对象和定义在根组件的_route
(暂时为空对象)
Object.defineProperty(Vue.prototype, "$router", {
get() {
return this._routerRoot._router
}
})
Object.defineProperty(Vue.prototype, "$route", {
get() {
return this._routerRoot._route
}
})
- 全局注册组件
RouterView
和RouterLink
// 注册全局组件
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
- 创建文件夹
components
,新建文件link.js
和view.js
,在install.js
中引入
link.js
RouterLink组件,实际上就是a标签,接收传入的to
属性值
to
的值可以是字符串或对象,是字符串时直接使用,是对象时取其中的path属性值router
在此时已经是代理好的VueRouter
实例了render
函数的返回值是h函数的调用,h
函数既为一个createElement
创建函数,作用是创建一个VNode
h
函数接收三个参数('html标签名',attribute属性值,子节点)
// link组件类似a标签,组件接受一个to参数
/*
* @path: src/hello-vue-router/components/link.js
* @Description: router-link
*/
export default {
name: 'RouterLink',
props: {
to: {
type: [String, Object],
require: true
}
},
render(h) {
const href = typeof this.to === 'string' ? this.to : this.to.path
const router = this.$router
let data = {
attrs: {
href: router.mode === 'hash' ? '#' + href : href
}
}
return h('a', data, this.$slots.default)
}
}
view.js
RouterView组件是一个占位符,这里使用函数式组件先看看效果。
/*
* @path: src/hello-vue-router/components/view.js
* @Description: router-view
*/
export default {
name: 'RouterView',
function: true, // 函数式组件
render(h) {
return h('div', 'This is RoutePage')
}
}
效果
可以看到view的内容,也可以切换路由了。
但是url还是没有带#号,暂不知原因,今日笔记先到这里,如有兴趣欢迎讨论。