vue-router简版的实现
首先,新建一个lrouter文件夹,新建一个index.js,里面的内容和我们平常代码中写的router配置一致, 唯一区别在于引入的router插件不同,使其正常显示即为实现完成.
// index.js
import Vue from 'vue'
import VueRouter from './lvue-router'
import Home from '../views/Home.vue'
// use方法内部会调用install(Vue)
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
children: [
{ path: 'a', name: 'AboutA', component: { name: 'AboutA', render: (h) => h('div', 'This is about/a') } },
{ path: 'b', name: 'AboutB', component: { name: 'AboutB', render: (h) => h('div', 'This is about/b') } }
]
},
{
path: '/info',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
children: [
{ path: 'a', name: 'InfoA', component: { name: 'InfoA', render: (h) => h('div', 'This is info/a') } },
]
}
]
const router = new VueRouter({
routes
})
export default router
前置工作完成,接下来进行vue-router的代码编写,实现一个VueRouter类以及一个install方法
// vue-router.js
let Vue
Class VueRouter {
constructor(options) {
this.$options = options
}
}
VueRouter.install = function(_Vue) {
// Vue.use(router)时第一个参数默认传入Vue,在VueRouter中需要使用,提前进行存储
Vue = _Vue
}
export default VueRouter
在vue-router中,我们需要根据对应路由进行组件切换,因此,我们需要在VueRouter中添加一个响应式的current属性作为当前路由信息,同时,我们需要在全局中注册$router
// vue-router.js
let Vue
Class VueRouter {
constructor(options) {
this.options = options
// current为响应式,因为VueRouter不为响应式,不能通过Vue.set设置,我们采用以下方式
Vue.util.defineReactive(this, 'current', getHash())
// 添加hashchange事件
window.addEventListener('hashchange', () => {
this.current = getHash()
})
}
getHash(){
return window.location.hash.slice(1) || '/'
}
}
VueRouter.install = function(_Vue) {
// Vue.use(router)时第一个参数默认传入Vue,在VueRouter中需要使用,提前进行存储
Vue = _Vue
Vue.mixin({
// 我们需要注册$router至根组件(router-view中需要获取$router信息以进行路由跳转),但是install(Vue.use(router))时,router还未完成挂载,因此采用混入方式,延迟执行注册操作
beforeCreate(){
if (this.$options.router){
Vue.prototype.$router = this.$options.router
}
}
})
Vue.component('router-link', link)
Vue.component('router-view', view)
}
export default VueRouter
接下来我们需要在install时实现router-link以及router-view组件
// link.js
// 分析: 我们的日常使用为<router-link to='/'>首页</router-link> 因此我们需要传入一个to属性,并且点击后跳转至相应链接(本文使用hash模式)
export defalut {
props: {
to: {
type: String,
required: true
}
},
render(h){
// h函数即为createElement,依次传入type, attrs, children
return h('a', {
attrs: {
href: '#' + this.to
}
// 获取插槽值并传入children, 此时其值为'首页'
}, this.$slots.default)
}
}
// view.js
export default {
render(h) {
let component = null
// 寻找到具有相同路径的route
const route = this.$router.$options.routes.find(route => route.path === this.$router.current)
if (route) component = route.component
// h函数也可以直接接受组件并且渲染
return h(component)
}
}
完成以上功能,单层路由的简单跳转就可以实现了,接下来进行嵌套路由跳转的实现
首先,我们需要实现一个新的方法 createMatcher
// create-matcher.js
// 入参: 路由表 返回值: 返回当前路由的所有层级的match函数
const createMatcher = (routes) => {
// pathMap: 一个含有所有路由的信息表,存储方式 {'/': {...路由信息}, '/about': {...}}
const pathMap = createRouteMap(routes)
const match = (path) => {
return createRoute(pathMap[path])
}
return { match }
}
// 返回当前路由的自身与祖先路由信息 如: /about/detail matched: [{/about 的路由信息}, {/about/detail的路由信息}]
function createRoute (route) {
const matched = []
while (route) {
matched.unshift(route)
route = route.parent
}
return matched
}
// 生成路由信息表
function createRouteMap (routes, pathMap = {}) {
addRouteRecord(routes, pathMap)
return pathMap
}
// 路由信息表的具体生成方式
function addRouteRecord (routes, pathMap, parent) {
routes.forEach(route => {
const { path, children, ...rest } = route
const normalizedPath = parent ? parent.path + '/' + path : path
pathMap[normalizedPath] = { ...rest, path: normalizedPath, parent }
if (children) addRouteRecord(children, pathMap, route)
})
}
export default createMatcher
由上,我们可得到一个路由信息表,以此重构我们的vue-router
// vue-router.js
class VueRouter {
constructor(options) {
// 生成路由信息表,并且获得match函数
this.matcher = createMatcher(options.routes)
// 原先的单个路由现在改为自身加祖先的所有路由信息,因此换为数组形式
Vue.util.defineReactive(this, 'matched', [])
// 获取当前的路由信息
this.matched = this.match(this.getHash())
// 监听路由变化
window.addEventListener('hashchange', () => {
this.matched = this.match(this.getHash())
})
}
getHash () {
return window.location.hash.slice(1) || '/'
}
// 获取当前路由的所有路由信息
match (path) {
return this.matcher.match(path)
}
}
最后再重构一下router-view
// view.js
export default {
render (h) {
// 新增routerView属性,若为true,即为已渲染
this.$vnode.data.routerView = true
let component = null
// 新增depth,判断当前路由为第几层级
let depth = 0
let parent = this.$parent
// 循环获取层级
while (parent) {
if (parent.$vnode && parent.$vnode.data && parent.$vnode.data.routerView) depth++
parent = parent.$parent
}
// 得到当前的所有路由信息
const { matched } = this.$router
// 获取当前层级的路由信息并渲染
const route = matched[depth]
if (route) component = route.component
return h(component)
}
}
以上就是手写简版vue-router的所有内容啦.实现了以下内容:
1. 实现了插件的安装,包括VueRouter类以及install方法
2. 其中VueRouter负责响应式的生成当前路由信息
3. install方法中,进行了$router的注册以及router-link, router-view两个全局组件的添加
4. link负责控制路由的变化,view根据路由信息找到相应的组件并渲染