一、首先配置路由表(router/index.js):
import Vue from 'vue';
import VueRouter from '../plugins/router'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/home',
component: () => import('../components/Home')
},
{
path: '/course',
component: () => import('../components/Course'),
children: [
{
path: 'vue',
component: () => import('../components/Vue')
},
{
path: 'react',
component: () => import('../components/React')
}
]
}
]
})
export default router;
二、手捏router(plugins/router.js):
- 定义一个响应式的数据列表 matched , 用来存储 路径 ['/home','/course','vue','react']
- 通过初始化init函数,获取到当前的路由模式,并进行监听(获取到current值)
- 调用match函数,将路由表进行递归处理,判断路由路径是否被当前current包含,包含的话,那么就存储在matched列表中,再次判断是否存在children属性,存在的话,那么就证明是嵌套路由,那么再次执行该函数,matched()进行处理。
import routerLink from '@/plugins/components/routerLink'
import routerView from '@/plugins/components/routerView';
let Vue;
class VueRouter {
constructor(options) {
// 保存选项
this.options = options;
// 定义一个响应式的变量 url, 保存当前的 hash 值
this.current = location.hash.slice(1,) || '/';
// defineReactive 定义响应式的对象
Vue.util.defineReactive(this, 'matched', [])
// 初始化 根据路由模式 切换不同的监听事件
this.init();
}
init() {
// 判断当前的路由模式
if (this.options.mode === 'hash') {
addEventListener('hashchange', this.changeHash.bind(this))
} else if (this.options.mode === 'history') {
addEventListener('popstate', this.changeHistory.bind(this))
}
this.match()
}
// hash 路由模式 进行切换 时触发 设置当前的current 为当前 路径
changeHash() {
this.current = location.hash.slice(1,)
this.matched = []
this.match()
}
// history 路由模式 进行路由切换时触发 准确来说时 进行左右箭头切换时触发
changeHistory() {
this.current = location.pathname;
this.matched = []
this.match()
}
match(routes) {
routes = routes || this.options.routes;
// 循环routes
routes.forEach(route=> {
if( route.path === '/' && this.current.includes(route.path) ) {
this.matched.push(route)
}
if( route.path !== '/' && this.current.includes(route.path)) {
this.matched.push(route);
if(route.children) {
this.match(route.children)
}
}
})
}
}
VueRouter.install = function (_Vue) {
// 1. 保存 Vue
Vue = _Vue;
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 创建 router-link
Vue.component('router-link', routerLink)
// 创建router-view组件
Vue.component('router-view', routerView)
}
export default VueRouter;
2.1 router-link组件:
- 通过router.options.mode 来判断当前的路由模式,来进行路由跳转。
- hash模式:直接返回 h('a', { attrs: { href: '#' + this.to } }, this.$slots.default) 即可,第一个参数是标签名,第二个参数是元素的属性,第三个参数是元素的内容。
- history模式:他在hash模式的基础上,需要添加一个on事件,就是点击事件,通过history.pushState来进行路由的跳转,将当前的current设置为跳转的目的地。通过调用this.$router.changeHistory()方法重新调用this.matched()方法获取到路由表映射。
export default {
props: {
// 接收参数 去向
to: {
type: String || Object,
required: true,
}
},
// h 有三个参数,第一个参数创建的元素,第二个参数元素具有的属性, 第三个参数元素的内容
render(h) {
const router = this.$router;
if( router.options.mode === 'hash' ) {
return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
} else if( router.options.mode === 'history' ) {
return h(
'a',
{
attrs: {
href: this.to
},
// history 进行跳转时需要自己手动进行点击事件
on: {
'click': ev => {
// 1. 阻止a链接的默认跳转行为
ev.preventDefault();
// 2. 调用pushState方法来实现跳转: pushState(obj, title, url)
history.pushState({}, '', this.to);
// 3. 设置 current 的值
router.current = this.to;
// 4.点击的时候再次调用changeHistory方法
this.$router.changeHistory()
}
}
},
this.$slots.default
)
}
}
}
2.2 router-view组件:
- routerView实现思路是通过判断虚拟dom上是否存在数据,存在的话,那么当前的层级就+1,没有的话,那么该层级为0,我们最后可以根据层级在this.matched中获取到当前的路径、对应的组件,进行渲染。
- 通过this.$vnode.data.routerView = true; 在当前虚拟dom上标记为true(留个记号)
- this.$parent获取到当前的父节点
- 通过while进行循环,判断vnodeData是否存在,并且上面存在routerView(咱们设置好的记号),第一层路由触发时,设置这层的虚拟dom为true,父节点的虚拟dom上没有参数,那么就不给depath++,那么为0;当是第二层路由触发时,那么他的父节点会有参数,那么就会进入到判断中,进行depath++,那么为1,在向上找,父节点没有虚拟dom,那么直接返回,最后depath为1。
- 通过matched列表使用depath来获取当前路由信息(路径和component)
export default {
name: 'routeView',
// 组件要重新渲染,必须要让数据发生改变的时候,重新去执行render函数
render(h) {
// 标记一下当前的组价是routeView组件
this.$vnode.data.routerView = true;
// 保存当前路由的层级
let depath = 0;
// 当前routeView组件的父节点
let parent = this.$parent;
while(parent) {
const vnodeData = parent.$vnode && parent.$vnode.data;
// 证明我当前找到的这个节点就是routeView组件
if(vnodeData && vnodeData.routerView) {
depath++;
}
// 不断向上寻找,查找到父节点,直到没有父节点了,退出循环。
parent = parent.$parent;
}
const router = this.$router;
let component = null;
let route = router.matched[depath];
if(route) {
component = route.component;
}
return h(component)
}
}