vue-router
目标:
- 实现⼀个插件
- 实现VueRouter类
- 处理路由选项
- 监控url变化,hashchange
- 响应这个变化
- 实现install⽅法
- $router注册
- 两个全局组件
- 实现VueRouter类
需求分析:
- spa ⻚⾯不能刷新
- hash #/about
- History api /about
- 根据url显示对应的内容
- router-view
- 数据响应式:current变量持有url地址,⼀旦变化,动态重新执⾏render
实现⼀个插件:创建VueRouter类和install⽅法
创建my-router.js
import LINK from './my-router-link'
import VIEW from './my-router-view'
let Vue
class VueRouter {
// 保存选项
constructor(options) {
this.$options = options
}
// 插件:实现install方法,注册$router
VueRouter.install = function (_Vue) {
Vue = _Vue
// 挂载$router
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 实现两个全局组件router-link和router-view
Vue.component('router-view', VIEW)
Vue.component('router-link', LINK)
}
export default VueRouter
//
let Vue
class VueRouter {
constructor(options) {
this.$options = options
}
}
为什么要用混入方式写?主要原因是use代码前,router实例创建在后,而install逻辑又需要用到该实例
创建router-view和router-link
创建my-router-link.js
export default {
props: {
to: String,
required: true
},
render(h) {
return h('a', {
attrs: {
href: '#' + this.to
}
}, [
this.$slots.default
])
}
}
创建my-router-view.js
export default {
render(h) {
//暂不渲染
return h(null)
}
}
监控url变化
import LINK from './my-router-link'
import VIEW from './my-router-view'
let Vue
class VueRouter {
constructor(options) {
// 定义响应式的属性current
const initial = window.location.hash.slice(1) || '/'
this.current = window.location.hash.slice(1) || '/'
Vue.util.defineReactive(this, 'matched', [])
// 监听hashChange事件
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
}
onHashChange() {
this.current = window.location.hash.slice(1)
}
}
动态获取对应组件 my-router-view.js
export default {
render(h) {
let component = null
const route = this.$router.$options.routes.find(route => route.path == this.$router.current)
if (route) component = route.component
return h(component)
}
}
提前处理路由表,避免每次都循环
class VueRouter() {
constructor(options) {
// 缓存path和route映射关系
this.routeMap = {}
this.$options.routes.forEach(route => {
this.routeMap[route.path] = route
})
this.current = window.location.hash.slice(1) || '/'
}
}
my-router-view.js
export default {
render(h) {
let component = null
// const route = this.$router.matched[depth]
const { routeMap, current } = this.$router
const route = routeMap[current]
return h(component)
}
}
子路由多层嵌套
标记当前router-view的depth(my-router-view.js)
export default {
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) {
if (vnodeData.routerView === true) {
depth++
}
}
parent = parent.$parent
}
const component = this.$route.matched[depth] ? this.$rout.matched[depth].component : null
return h(component)
}
}
my-router.js
class VueRouter {
constructor(options) {
// 缓存path和route映射关系
// this.routeMap = {}
// this.$options.routes.forEach(route => {
// this.routeMap[route.path] = route
// })
this.current = window.location.hash.slice(1) || '/'
Vue.util.defineReactive(this, 'matched', [])
this.match()
}
onHashChange() {
this.current = window.location.hash.slice(1)
this.matched = []
this.match()
}
match(routes) {
routes = routes || this.$options.routes
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) {
this.match(route.children)
}
return
}
}
}
}