前言
手动实现一个vue-router,了解基本的原理。
插件注册
vue-router是使用Vue.use的方式注册
Vue.use就是执行你传入的对象或者函数上是否含有instal方法,有则直接执行,将Vue作为第一个参数传入
Vue.use = function (plugin: Function | Object) {
// ...
// 处理参数,第一个参数不要,再往前面放一个Vue构造函数
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)
}
return this
}
vue-router的基本使用
下面是使用的核心代码
// 1.注册一下vueRouter的插件,实际上执行是vueRouter.install方法
Vue.use(vueRouter)
// 2.一个路由的配置表
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About,
}
]
// 3.实例化routes
const router = new VueRouter({
routes
})
new Vue({
router
})
// 4.在组件中使用
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
实现思路
vue-router在使用之后,个vue实例上都多了一个$store属性,可以使用router-link和router-view组件,同时每,这些都是在Vue.use时,执行install方法作用的。
1.install方法如下
- 给每个vue实例添加一个$store, install方法,Vue.mixin混入beforeCreate生命周期
- install方法中,注册两个全局组件router-link和router-view
- router-link组件,渲染出一个a标签,将传入的to属性,设置到a标签的href属性上
- router-view组件,根据路由表通过render函数,找出对应的组件渲染出来 2.组件的动态切换,监听popState/hashchange方法,路由改变时,实现组件更新,这个可以利用vue的响应式来实现
基础版的代码实现
let Vue
export default class VueRouter {
constructor(options) {
this.$options = options
// 将current的属性变成响应式,router-view组件有使用到current,所以当current变化时,outer-view组件会自动更新,这也是vue本身的特性。
Vue.util.defineReactive(this, 'current', window.location.hash.slice(1) || '/')
// 使用hashchange事件,在谷歌浏览器上赋值不成功了,改成popstate事件
window.addEventListener('popstate', ()=>{
this.current = window.location.hash.slice(1)
})
this.map = {}
options.routes.forEach(route=>{
this.map[route.path] = route.component
})
}
}
VueRouter.install = function(_Vue) {
Vue = _Vue
Vue.mixin({
beforeCreate() {
// router传入vue的根实例,被赋值给原型,所以的vue实例都拥有$router
if(this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
Vue.component('router-link', {
props: {
to: String,
required: true
},
render(h) {
// 通过render函数,渲染a标签,a标签的内容是在默认插槽中,通过this.$slots.default取出
return h('a', {attrs: {href: '#'+this.to}}, this.$slots.default)
}
})
Vue.component('router-view',{
render(h) {
let component = null
component = this.$router.map[this.$router.current] || null
return h(component)
}
})
}
路由嵌套版的实现
假如在About组件中,还有一个router-view,再去请求/about,就会一直渲染About组件,栈溢出。所以实现起来稍微复杂一些
使用
// 路由表
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About,
children: [
{
path: '/about/info',
component: {
render(h) {
return h('div', '我是children组件')
}
}
}
]
}
]
// about组件
<template>
<div class="about">
<h1>This is an about page</h1>
<router-view/>
</div>
</template>
实现
思路:当访问/about/info时,将/about/info自身组件和它所有父级的组件都匹配到一个数组里。每次渲染router-view组件时,做一个标记,最后计算路由嵌套的层数,取出对应的组件渲染。
let Vue
export default class VueRouter {
constructor(options) {
this.$options = options
this.current = window.location.hash.slice(1) || '/'
Vue.util.defineReactive(this, 'matched', [])
this.match()
window.addEventListener('popstate', ()=>{
this.current = window.location.hash.slice(1)
this.matched = []
})
this.map = {}
options.routes.forEach(route=>{
this.map[route.path] = route.component
})
}
match(routes) {
routes = routes || []
for(const route of routes) {
// 首页,假设没有路由嵌套
if(route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}
// 匹配到路由所有的父级路由,push到一个数组里
if(route.path !== '/' && this.current.indexOf(route.path) != -1) {
this.matched.push(route)
if(route.children) {
this.match(route.children)
}
return
}
}
}
}
VueRouter.install = function install(_Vue) {
Vue = _Vue
// 全局混入
// ...
//
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) {
depth++
}
parent = parent.$parent
}
// 获取当前路由对应的组件
let component = null
onst route = this.$router.matched[depth]
if(route) component = route.component
return h(component)
}
})
}
最后
大家对于vue-router的实现,是否有一个初步的了解呢,当然要深入了解还是要自己去看源码实现,欢迎点赞~