这是我参与更文挑战的第 4 天,活动详情查看: 更文挑战
2021-06-04 原创 TANGJIE Vue2 Router
宝刀未老,谈谈Vue2的Router
1. Vue-Router基础和问题抛出
1.1 vue使用router插件
在官方给出的文档中,说到,安装好npm后,需要Vue.use(VueRouter),具体代码:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
在vue中使用插件就是使用Vue的use方法。
所以第一个问题来了?这个use做了什么事情?Vue.use(VueRouter)为什么是这样使用插件?
1.2 router路由文件和路由表
用官方给的cli快速创建一个项目,选上router,这时候router/index.js里面代码是这样的
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
routes
})
export default router
然后再其他路由组件当中又能通过this.$router去访问到Router的实例,通过这个实例可以在函数级别的来进行路由切换(具体方法看文档不赘述)。那么由此带来了第二个问题,this.$router是怎么实现的?
1.3 router-link/router-view的使用
使用路由除了建立路由表,还需要搭配<router-view></router-view>,如果是普通的router切换使用<router-link to="/">首页</router-link> 即可,所以第三个问题,如何实现router-view和router-link?
2. 发现问题解决问题,手写一个简单的vue-router插件
2.1 Vue.use
在vue中使用插件就是使用Vue.use(xxx)
import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
上面是一段Vue.use的代码,从代码当中,可以清楚的看到Vue.use
- 判断这个插件是否被注册过,不允许重复注册,并且接收的 plugin 参数的限制是 Function | Object 两种类型
- 将 Vue 对象添加到这个数组的起始位置
args.unshift(this),这里的this指向 Vue 对象 - 传入的
plugin(Vue.use的第一个参数) 的install是一个方法。也就是说传入一个对象,对象中包含install方法,那么我们就调用这个plugin的install方法并将整理好的数组当成参数传入install方法中,plugin.install.apply(plugin, args); 如果我们传入的plugin就是一个函数,那么我们就直接调用这个函数并将整理好的数组当成参数传入,plugin.apply(null, args); 之后给这个插件添加至已经添加过的插件数组中,标示已经注册过installedPlugins.push(plugin); 最后返回Vue对象。
因此写Vue插件,此插件对象需要一个install方法,好第一个问题解决~
2.2 Router插件的this.$router是怎么实现
看到这个很多人会想当然,啊,这不就是 Vue.prototype.$router = new Router(xxx)就完事了吗?但事实真是如此?
在vue的router使用里面,Vue.use(Router)是在Router实例化之前的,还没有进行new Router这个操作,但是已经调起了install这个方法,所以啊,我们需要用Vue.mixins({})使用beforeCreate,在这里面去获取Router的实例,当然其实用setTimeout实现也是可以的。
2.3 插件注册组件
其实这个就挺简单了,直接使用Vue.component去注册即可
主要问题点解决,是不是对实现一个简单的Router有了一个清晰的思路
3. 简单Router示例代码
// 因为vue-router是插件
// 1. 要实现一个install方法,只有实现了install这个方法,Vue.use才能正确的使用装载这个插件
let VueConstructor; // 将vue的构造函数保存为当前文件下的全局变量
class Router {
constructor(options = {}){
this.options = options;
// Router实例化后默认的path为根路径
// this.current = {path:'/'}; 让current变成响应式
this.current = VueConstructor.observable({
path:'/'
})
window.addEventListener('hashchange',()=>{
this.current.path = window.location.hash.slice(1);
})
window.addEventListener('load', ()=>{
this.current.path = window.location.hash.slice(1);
})
}
/**
* 装载方法 定义一个install的静态方法
* @params { VueConstructor } vue vue的构造函数
* */
static install (Vue){
// 这么处理的重要的一个作用是避免打包的时候把vue文件打包进去,保持插件的独立性
VueConstructor = Vue
// 1.挂载 $router 指向 Router的实例
// Vue.prototype.$router = this; 因此有些同学就理所当然的这样去用了
// 其实这是不对的,此时this指向的是Router 这个类而非Router的实例
// 那怎么挂载Router的实例呢? 从官方的router使用上来看
// 是先Vue.use(VueRouter) 然后创建路由表,然后将其实例化,在加载到new Vue当中去的
// 所以这就出现了一个问题 install的执行肯定比 Router自身实例化要快
// 所以该如何解决这个问题也需要一定的思考
// 解决这个问题的方法可以使用Vue.mixin 配合其生命周期beforeCreate生命周期
// 当然一个很low的方法用setTimeOut这也是可以实现的
Vue.mixin({
beforeCreate(){
/**
* 被实例化的router是这样被引入到Vue根实例选项当中的,也就是会加
* 载到根实例的$options这个属性上去,而其他vue实例没有,因此挂载
* $router 指向 Router的实例,就这样被解决了
* new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
* */
if( this.$options.router ){
Vue.prototype.$router = this.$options.router;
}
}
})
// 2. 实现router-view和router-link全局组件
Vue.component('router-link',{
render(h){
console.log( this ,'router-link 的 this')
/**
* 创建虚拟节点 h函数的参数
* @param { string } tag
* @param { VNodeData } data
* @param { Array<VNode> } children
* @param { string } text
* @param { Node } elm
* @param { Component} context
* @param { VNodeComponentOptions } componentOptions
* @param { Function }asyncFactory
* 以上参数均是可选
**/
return h('a',{
attrs:{href:`#${this.$attrs.to}`}
// 在<router-link to="/">Home</router-link>中的文本插入的地方
// 其实就是该元素的默认插槽(这部分不清楚的请移步到vue文档插槽部分
// 都2021了还不知道插槽怎么用???)
},this.$slots.default)
}
});
Vue.component('router-view',{
render(h){
console.log( this ,'router-view 的 this')
let component;
if( this.$router.options.routes ){
const routes = this.$router.options.routes;
routes.forEach( item =>{
if( item.path === this.$router.current.path){
component = item.component
}
})
// 完成到这里基本上渲染路由组件是没什么问题的,但是关键的一个来了,
// 如何实现 this.$router.current 的响应式处理,动态切换路由组件呢
// 这时候就需要借助 Vue.observable
/**
* Vue.observable
* 让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
* 返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新(会触发render函数的执行)。
* 也可以作为最小化的跨组件状态存储器,用于简单的场景
* */
return h(component)
}
}
});
}
}
// 可以用这种方式定义精态方法
//Router.install = function( Vue ){
// ...
//}
export default Router
这样一个简单的hash的router插件就完成了,新手也能看懂实现方式~ 是不是 收获满满~ 点赞 点赞 求点赞~