如何实现vue-router

189 阅读1分钟

vue-router使用

import VueRouter from 'vue-router'

// 1、引入
Vue.use(VueRouter) // 插件install


const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  }
]

// 2、new对象
const router = new VueRouter({
  routes
})

// 3、放到opstions里面
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

实现一个简版

my-router.js

类vue-router

1、构造函数,保存options、设置初始值,作响应式,加监听

let Vue

class VueRouter {
	// 构造器
	constructor(options) {
		// 保存options
		this.$options = options

		// 设初值
		const initVal = window.location.hash.slice(1) || '/'
		this.current = initVal
		console.log('this.current', this.current)

		// 作响应式
		Vue.util.defineReactive(this, 'matched', [])
		// this.current = window.location.hash.slice(1) || '/'

		// 加监听
		this.hashChange = this.hashChange.bind(this)
		window.addEventListener('hashchange', this.hashChange)
	}

	// 监听的值变化实际操作
	hashChange() {
		this.current = window.location.hash.slice(1)
		this.matched = []
		this.match() // 重新匹配
	}

	match(routes) {
		// 一般直接取options
		routes = routes || this.$options.routes

		// match的会生成一个数组
		// 比如 /about/info
		// match会生成 ['/about', '/about/info'] // 实际存的是对象
		for (const route of routes) {
			if (route.path === '/' && this.current === '/') {
				this.matched.push(route)
				return
			}
			if (route.path !== '/' && this.current.indexOf(route.path) > -1) {
				this.matched.push(route)
			}
			if (route.children && route.children.length > 0) {
				route.children.forEach((ele) => {
					if (ele.path !== '/' && this.current.indexOf(ele.path) > -1) {
						this.matched.push(ele)
					}
				})
			}
		}
	}
}

2、install 方法实现,直接挂在静态下

VueRouter.install = function(_Vue) {
	Vue = _Vue
	
	// 利用全局混入,延迟执行下面的代码,这样可以获取router实例
	Vue.mixin({
		beforeCreate() {
			// 组件实例
			if (this.$options.router) {
				Vue.prototype.$router = this.$options.router
			}
		},
		beforeDesotry() {
		}
	})
	
	// 注册组件 router-link、router-view实现
	// ....
	
}

	
export default VueRouter;

router-link 实现

Vue.component('router-link', {
	props: {
		to: {
			type: String,
			required: true
		}
	},
	render(h) {
		return h(
			'a',
			{
				attrs: {
					href: '#' + this.to
				}
			},
			this.$slots.default
		)
	}
})

router-view 实现

Vue.component('router-view', {
	render(h) {
		this.$vnode.data.routerView = true
		let depth = 0
		let parent = this.$parent
		while (parent) {
			const vnodeData = parent.$vnode && 
			parent.$vnode.data
			if (vnodeData && vnodeData.routerView) {
				// 说明当前parent时routerview
				depth++
			}
			parent = parent.$parent
		}
		
		console.log('执行-routerview渲染--')
		console.log('this.$router.current' + '比较')
		
		let component = null
		
		// 获取path对应的component
		const route = this.$router.matched[depth]
		console.log(depth)
		console.log(this.$router.matched)
		if (route) {
			component = route.component
		} else {
			component = this.$router.$options.notFund.component
		}
		// console.log(component)
		return h(component)
	}
})

3、vue-router源码里是如何生成matched的

在util/route下存在以下方法

function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
  const res = []
  while (record) {
    res.unshift(record)
    record = record.parent
  }
  return res
}

4、源码vue-router问题解析

问题1:每个组件都是怎么拿到router的?

Vue.mixin 在beforeCreate时给根vue挂了个_router

Vue.util.defineReactive(this, '_route', this._router.history.current)

子组件在constructor的时候会走这一步:

this._routerRoot = (this.$parent && this.$parent._routerRoot) || this

从 this._routerRoot 获取

  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })
问题2:如何做响应式的?(即push后页面会进行替换)

源码位置:src\index.js

根vue实例里作了响应式

Vue.util.defineReactive(this, '_route', this._router.history.current)

vueRoutr对象内有个apps数组,存的是根实例。

(为什么是数组,大概是处理多页面应用?)

init时会添加个监听函数,当history的current变化时会同步修改 _route

history.listen(function (route) {
  this.apps.forEach(function (app) {
    app._route = route;
  });
});