这是我参与更文挑战的第1天,活动详情查看: 更文挑战
学习了vue-router原理,记录一下,方便加深记忆。感谢村长!
通过vue-router的使用方式创建测试用例
// 1. 在./frouter/index.js 引入router插件并使用
import FRouter from './frouter.js'
Vue.use(FRouter)
// 2.在./frouter/index.js创建router实例并导出
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// 注意此处必须是以函数形式引入
component: () => import('../views/About.vue'),
children: [
{
path: '/about/test',
name: 'Test',
component: {
render (h) {
return h (
'div',
null,
['我是嵌套路由啊~']
)
}
}
}
]
}
]
export default new FRouter({ routes })
// 3.在main.js中引入router实例并挂载到Vue实例
import router from './frouter'
new Vue({
router
}).$mount('#app')
// 4.在App.vue中添加路由导航和路由视图
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/about/test">嵌套路由</router-link>
<router-view></router-view>
// 5.在About.vue中添加路由视图
<router-view></router-view>
思考实现方式
创建frouter插件
- 在插件中实现FRouter类
- 保存并处理router实例传过来的路由配置表
- 监控路由的变化并保存当前路由的信息
- 对当前路由做响应式处理,以便在路由变化的时候驱动路由视图进行变化
- 在插件中实现install方法提供给Vue.use()使用
- 将$router写入Vue的原型链中,以便在所有组件中通过$router实例进行访问
- 全局注册router-link组件与router-view组件
- 新加一个嵌套路由 2021/6/2 17:19
- 标记当前路由视图的深度
- 在路由匹配的时候获取深度层级的matched数组
- 根据深度在matched数组中获取当前路由及组件
// 创建frouter.js
let Vue;
class FRouter {
constructor (options = {}) {
this.$options = options
// this.mapRouter = {}
// 保存path与route之间的映射关系,避免每次都需要循环查找
// this.$options.routes.forEach(route => {
// this.mapRouter[route.path] = route
// });
// 获取hash值并截取#后面的值
// const initialCurrent = window.location.hash.slice(1) || '/'
this.current = window.location.hash.slice(1) || '/'
// 使用Vue的工具函数defineReactive对当前路由存放的matched数组做响应式处理
Vue.util.defineReactive(this, 'matched', [])
// match方法可以递归遍历路由表,获得当前路由及其存在的子路由的数组,按照深度进行排序
// 初始化的时候匹配依次
this.match()
// 此处的this指的是当前类
// Vue.util.defineReactive(this, 'current', initialCurrent)
// 监听hashchange事件并绑定当前类作为上下文以防外界在定时器中访问onHashChange方法时改变this指向
window.addEventListener('hashchange', this.onHashChange.bind(this))
}
onHashChange () {
this.current = window.location.hash.slice(1)
// 每次路由变化的时候清空matched数组并重新匹配
this.matched = []
this.match()
}
match (routes) {
// 递归,所以传值,如果传了就用传的,没传就用用户定义的路由表
routes = routes || this.$options.routes
routes.forEach(route => {
// 判断循环的路由是否是根路由且当前路由也是根路由
if (route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}
// /about 或 /about/test
if (route.path !== '/' && this.current.indexOf(route.path) !== -1) {
this.matched.push(route)
if (route.children) {
this.match(route.children)
}
return
}
})
}
}
FRouter.install = function (_Vue) {
// 传入Vue的构造函数方便修改它的原型,绑定$router
Vue = _Vue; // 将传入的_Vue构造函数保存以便在FRouter类中使用Vue中的工具函数
// 通过全局混入将router注入到Vue的原型链中
Vue.mixin({
// Vue中的所有组件都会进入beforeCreate生命周期
// 判断只有当前组件实例中传入了router实例才将router实例写入到Vue的原型链中(查看测试用例中的第3点)
beforeCreate () {
// 此处的this指的是当前的组件实例
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 全局注册router-link组件
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
}
},
render (h) {
// 在runtime运行环境中不能放<template>标签,必须用render函数渲染
return h ('a', {
attrs: {
href: '#' + this.to
}
}, this.$slots.default)
}
})
// 全局注册router-view组件
Vue.component('router-view', {
render (h) {
// 给当前路由视图做标记,标明是router-view
this.$vnode.data.routerView = true;
// 定义当前路由深度
let depth = 0;
// 定义当前路由的父路由视图
// 注意:为何不是从当前路由一层一层的循环找子路由?
// 因为当前只可能显示一个路由(不管它是父路由还是子路由)
// 如果依次查找子路由,可能会在matched数组中存在多分枝关系
// eg: a父路由, b子路由,c是b的兄弟路由,d是b的子路由,e是c的子路由,不好处理
// 而如果从当前路由依次查找父路由,则每次查找都只会查到一个父路由
// 所以每次重新match,更新matched数组后,可以按照深度来直接查找到当前路由的组件进行渲染
let parent = this.$parent
// 依次对父路由进行循环判断,如果是router-view,则当前路由深度+1
while (parent) {
// 注意此处必须判断父路由视图是否存在虚拟dom
const vNodeData = parent.$vnode && parent.$vnode.data
if (vNodeData && vNodeData.routerView) {
depth++
}
// 将父路由赋值为祖父路由,再次判断
parent = parent.$parent
}
// 此处的this指的是组件实例,通过vue组件实例访问路由$router实例中的$options与current及mapRouter
let component = null;
const route = this.$router.matched[depth]
if (route) {
component = route.component
}
// const { mapRouter, current } = this.$router
// const component = mapRouter[current] ? mapRouter[current].component : null
return h(component)
}
})
}
// 导出插件
export default FRouter;