宝刀未老,谈谈Vue2的Router

宝刀未老,谈谈Vue2的Router

技术杂谈小彩虹2021-08-23 23:24:53170A+A-

这是我参与更文挑战的第 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-viewrouter-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

  1. 判断这个插件是否被注册过,不允许重复注册,并且接收的 plugin 参数的限制是 Function | Object 两种类型
  2. 将 Vue 对象添加到这个数组的起始位置 args.unshift(this) ,这里的 this 指向 Vue 对象
  3. 传入的 plugin(Vue.use的第一个参数) 的 install 是一个方法。也就是说传入一个对象,对象中包含 install 方法,那么我们就调用这个 plugininstall方法并将整理好的数组当成参数传入 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插件就完成了,新手也能看懂实现方式~ 是不是 收获满满~ 点赞 点赞 求点赞~

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们