VueRouter3的hash模式主要原理:
- URL 中 # 后面的内容作为路径地址
- 监听 hashchange 事件
- 根据当前路由地址找到对应组件重新渲染
在router文件夹下新建myrouter.js
在router文件夹下的index.js中,将引入的vuerouter更改为myrouter.js
import Vue from 'vue'
import VueRouter from './my-router'
import HomeView from '../views/HomeView.vue'
处理install方法
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
const router = new VueRouter({
routes
})
export default router
Vue.use内部传入对象时,需要提供一个install方法,并且最后默认导出
export default class VueRouter {
// 给Vue添加全局功能 全局注册组件 prototype上加属性
static install (Vue) {
...
}
}
全局注册组件
全局注册router-link和router-view两个组件,并且通过render函数去渲染它们
render函数
主要是用来创建虚拟DOM,参数h则通过vue传递
h函数
一共接收3个参数:
- 节点类型 - 创建这个元素的选择器
- 节点属性 - 给这个标签设置属性
- 内部节点 - 生成元素的子元素
export default class VueRouter {
static install (Vue) {
Vue.component('router-link', {
render(h) {
return h('a', 'router-link')
}
})
Vue.component('router-view', {
render(h) {
return h('div', 'router-view')
}
})
}
}
router-link组件
- 配置
h函数的第二个参数 - 给a标签设置属性 - 默认插槽处理
Vue.component('router-link', {
props: {
to: String
},
render(h) {
return h('a',{
attrs: {
href: '#' + this.to, // 因为是hash模式拼接#
}
}, this.$slots.default) // 第三个参数传入默认插槽
}
})
router-view组件
直接在h函数中传入组件
import HomeView from '../views/HomeView.vue'
class VueRouter {
...
Vue.component('router-view', {
render(h) {
return h(HomeView)
}
})
}
constructor构造函数
记录构造函数中传入的选项 options
constructor (options) {
this.options = options
console.log('options', options)
}
监听 hashchange 处理逻辑
- 在构造函数中,监听hashchange变化
- 设置一个current属性,初始值为这个地址的hash值
constructor (options) {
this.options = options
this.current = window.location.hash.slice(1) || '/'
window.addEventListener('hashchange', () => {
const hash = window.location.hash
this.current = hash
})
}
VueRouter实例挂载在Vue的原型上
原型上添加 $router 把router实例挂载在prototype的$router
- 在main.js中,App根组件实例化时,传入了router实例
- 通过生命周期钩子函数beforeCreate拿到router实例
static install (Vue) {
// 通过全局混入Vue.mixin来执行beforeCreate函数
Vue.mixin({
beforeCreate() {
// if判断 当只有进入根组件的时候 才可以获取到router实例
if(this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
}
嵌套路由实现
- 编写二级路由
- 配置二级路由出口,在
AboutView.vue中
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
children: [
{
path: '/about/info',
component: {
render(h) {
return h('h1', 'About Info页面')
}
}
}
]
<template>
<div class="about">
<h1>This is an about page</h1>
<!-- 新写的二级路由出口 -->
<router-view />
</div>
</template>
实现depth
核心是去标记深度
render(h) {
// 标记这个组件是routerView
this.$vnode.data.routerView = true
// 默认深度为0
let depth = 0
// parent 寻找当前父组件
let parent = this.$parent
// while条件一直网上找父组件 直到找不到为止
while(parent) {
const vnodeData = parent.$vnode ? parent.$vnode.data : {}
// 如果是你标记的routerView组件 层级++
if(vnodeData.routerView) {
depth++
}
// 父组件的父组件 如果存在的话会再次进入while循环
parent = parent.$parent
console.log(depth)
}
}
实现matched
将matched处理成响应式
- 不能使用Vue.set 因为set的语法第一个参数本身就是需要响应式的对象
- 不能使用Object.defineProperty,因为这个是数据劫持,无法实现响应式的功能
// 定义一个变量_Vue
let _Vue
export default class VueRouter {
constructor (options) {
this.options = options
this.current = window.location.hash.slice(1) || '/'
// defineReactive(对象, 对象想要响应式的键值, 初始值)
_Vue.util.defineReactive(this, 'matched', [])
// 新增调用match方法
this.match()
window.addEventListener('hashchange', () => {
const hash = window.location.hash
this.current = hash
})
}
static install (Vue) {
_Vue = Vue
...
}
}
提供match方法
- 处理matched添加路由规则 this.matched.push
- 递归 route.children
match(routes) {
// 如果不传routes 默认使用options的routes
routes = routes || this.options.routes
for (const route of routes) {
// 如果路径为首页,直接向matched数组里添加路由规则
if(route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}
// current: /about/info
// matched: [{about路由规则}, {aboutinfo路由规则}]
// route.path: /about /about/info
// 如果路径不为首页,当前路由包含路由规则
if(route.path !== '/' && this.current.includes(route.path)) {
this.matched.push(route)
// 如果路由规则属性有children
if(route.children) {
// 递归
this.match(route.children)
}
}
}
}
根据depth匹配对应的路由规则
render(h) {
...
// console.log(depth)
// 根据路由规则 url匹配的页面组件去渲染
let component = null
const route = this.$router.matched[depth]
if(route) {
component = route.component
}
return h(component)
}
hashchange后重置matched数组并且重新调用match匹配规则
切换路由后,无法重新渲染页面组件,需要在hashchange进行重置
window.addEventListener('hashchange', () => {
const hash = window.location.hash.slice(1)
this.current = hash
// 重置 matched数组清空 并且重新匹配
this.matched = []
this.match()
})
嵌套路由完成