最近在整理以前收藏的学习内容,发现了别人写的这个 vue-router 原理,自己也动手敲了一遍,做个记录
写 vue-router 之前,确定需求;
要实现的功能
- 所有子组件可以访问
$router和$route - 实现
router-view和router-link - 实现
mode为hash和history - 实现路由的响应式变化
要想实现以上的需求,就需要对 vue-router的结构有个清晰的认知
vue-router 在 main.js中的使用
//main.js
import router from './router'
new Vue({
router, // 重点
render: function (h) { return h(App) }
}).$mount('#app')
vue-router 在 router 中
import Vue from 'vue'
import VueRouter from './TSK'
import Home from '../views/Home.vue'
import About from "../views/About.vue"
Vue.use(VueRouter)
const routes = [
{
path: '/home',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
];
const router = new VueRouter({
mode: "hash",
routes
})
export default router
可以很清楚的看到 export default出去了一个 new VueRouter 实例,也就是需要一个 VueRouter 是一个 class 类,这个类接受 mode模式和 routes数组;
Vue.use(vueRouter) 当使用 vue.use注册插件的时候,可以接收两种方式
- 一个对象,里面有一个函数 叫做
install - 一个函数
不论是什么,vue 会把自己 注入到 这个函数里,当做第一个参数;
前期工作已经做完,开始写代码 - 构造 Router 类,接收 mode 和 routes
class MyRouter {
constructor(options) {
//容错处理
this.mode = options.mode || "hash"
this.routes = options.routes || [];
}
}
MyRouter.install = function (vue){
}
export default MyRouter
- 把只有在 根实例 才有的 router 分发给自己的子节点(重点)
// 只有 new vue 里面有 一个 router ,但是其他组件都可以使用
// router 是全局路由器 ,route 是当前页面路由
MyRouter.install = function (vue) {
vue.mixin({
beforeCreate() {
if (this.$options && this.$options.router) {
// 这个地方巧妙
// 在自身上挂载了 _root 属性,只有根节点的 $options 上才有
// router 属性,把它挂载到 自己的 _router属性上
this._root = this;
this._router = this.$options.router;
} else {
// 其余节点都不是根节点,没有 options 上的 router,只能父级身上找
// 子-父节点的创建顺序
// 父:beforeCreate 子:beforeCreate 子:created 父:created
//因为混入是在 beforeCreate 所以可以获取到 父节点的 _root
this._root = this.$parent && this.$parent._root
}
// 经过混入,所有子节点都指向了 原始的根节点
// 为了避免更改,使用 只读属性
Object.defineProperty(this, "$router", {
get() {
return this._root.router
}
})
},
})
- 开始整理
routesroutes 中一个 path 和 一个 component 一一对应, 在 vueRouter 中 routes 为了更好的表达父子关系,用了树结构
但是在内部,为了方便要转换成一个对象,对象的 key 值 是 path, value 值是 component
createMap(routes) {
return routes.reduce((prev, cur) => {
prev[cur.path] = cur.component;
return prev
}, {})
}
在 类中的 constructor 中生成 routesMap 这个对象,并且开始 init 初始化
// constructor
this.routesMap = this.createMap(this.routes)
this.history = new HistoryRoute();
this.init()
HistoryRoute (不是重点)
class HistoryRoute {
constructor() {
this.current = null
}
}
init 生成 current 路径
init() {
if (this.mode == "hash") {
// 给一个默认值
location.hash ? '' : location.hash = "/";
window.addEventListener("load", () => {
this.history.current = window.location.hash.slice(1)
})
window.addEventListener("hashchange", () => {
this.history.current = window.location.hash.slice(1)
})
} else if (this.mode == "history") {
location.pathname ? '' : location.pathname = "/";
window.addEventListener("load", () => {
this.history.current = window.location.pathname
})
window.addEventListener("popstate", () => {
this.history.current = window.location.pathname
})
}
}
- 有了 当前的 path 和映射对象,获取 router-view 要渲染的component
vue.component("router-view", {
render(h) {
let current = this._root._router.history.current
console.log(this);
let routeMap = this._root._router.routesMap;
return h(routeMap[current])
},
});
- 一个简单的 router-link
vue.component('router-link', {
props: {
to: String
},
render(h) {
let mode = this._root._router.mode;
let to = mode === "hash" ? "#" + this.to : this.to
return h('a', { attrs: { href: to } }, this.$slots.default)
}
})
- 最后但是最重要的一点,要把路由变成响应式 vue.util.defineReactive 是一个内部属性方法
//vue.mixin -> beforeCreate
vue.util.defineReactive(this, "xxx", this._router.history)
note:通过手写 vue-router 了解到自己对 vue 的认知还是很浅的,还有欠缺一些大型架构的能力,以后要不断的去读源码,去模仿;date:2022.6.3 端午节
vueRouterGit地址
B站老师详细解释
date:2022.6.4