
本项目有vue-cli创建,package.json内容如下
{
"name": "myvuerouter1",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^2.6.14",
"vue-router": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"vue-template-compiler": "^2.6.14"
}
}
当我们使用vue-router插件时会有两步
Vue.use(VueRouter) 注册组件new VueRouter() 实例一个VueRouter类,同时把路由信息传给实例import Vue from 'vue'
import VueRouter from '../vuerouter'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = new VueRouter({
mode: 'hash',
routes
})
export default router;
当执行 Vue.use(VueRouter) 时会自动调用 VueRouter 类的 install 方法。所以我们应该定义 VueRouter 类,并且其上有 install 方法。
大方向好了,接下来我们来思考一下 install 方法和 VueRouter 类具体做了什么事。
install方法 : 会注册一些全局组件调用 Vue.mixin() 方法使每个组件都可以获取 router 属性,这里是重点难点,具体看代码注释。在Vue对象原型上定义 响应式的$router 和 $route 属性,便于全局进行路由 *** 作。install.js
import routerLink from "./components/router-link";
import routerView from "./components/router-view";
export let Vue = null;
export const install = function (_Vue) {
Vue = _Vue;
// 注册组件
Vue.component('router-link', routerLink)
Vue.component('router-view', routerView)
// 每个子组件都获取router属性
Vue.mixin({
// 如果有router 说明你在跟实例增加了router
beforeCreate() {
if (this.$options.router) {
this._routerRoot = this;//将当前根实例Vue放到了_routerRoot
this._router = this.$options.router; // 当前VueRouter实例
// 调用初始化函数
this._router.init(this);
// 把路由_route属性变成响应式的
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
// 将根组件的_routerRoot赋值个子组件的_routerRoot
this._routerRoot = this.$parent && this.$parent._routerRoot;
}
}
})
// vue原型上添加两个响应式属性
Object.defineProperty(Vue.prototype, '$route', {
get() {
return this._routerRoot._route;//取current
}
})
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot._router;//取current
}
})
}
VueRouter类:
在构造函数中定义matcher属性,它是一个函数,此函数有两个功能,匹配路径对应的记录,添加新的路由,在该函数首先会先调用 createRouteMap 方法创建路径与记录的映射关系。创建历史管理,选择路由的模式hash还是history。定义 match 方法,返回路径对应的记录定义 init 方法,初始化监听 hash 变化的方法,跳转方法,注意这里是难点要点
index.js
import { install } from './install';
import createMatcher from './create-matcher'
import BrowsHistory from './browsHistory';
import HashHistory from './hashHistory';
class VueRouter {
constructor(options) {
// 本函数两个功能:匹配路径对应记录功能match, 添加匹配功能addRoutes
this.matcher = createMatcher(options.routes || []);//获取用户的整个路由配置
// 创建历史管理 路由两种模式, 默认是hash
this.mode = options.mode || 'hash';
switch (this.mode) {
case 'history':
this.history = new BrowsHistory(this);
break;
case 'hash':
this.history = new HashHistory(this);
break;
}
}
// 返回路径对应的记录
match(location) {
return this.matcher.match(location);
}
init(app) {//app指代最外层的Vue
const history = this.history;
// 设置onHashChange,监听hash变化
let setupHashLisener = () => {
history.setupHashLisener();
}
// 跳转路径,会进行匹配 *** 作根据路径获取对应的记录
history.transitionTo(history.getCurrentLocation(), setupHashLisener);
// 当跳转后更新_route,将回调函数传入
history.listen(route => {
app._route = route;//current更新后在更新_route
})
}
}
VueRouter.install = install;
export default VueRouter;
VueRouter的构造函数第一步就是获取两个方法:匹配路径对应记录功能 match, 添加匹配功能 addRoutes,同时调用 createRouteMap 方法将传入的路由信息格式化成一个路径记录的的映射关系。
create-matcher.js
import createRouteMap from './create-router-map';
import { createRoute } from './base';
export default function createMatcher(routes) {
// pathList保存所有的路由路径,同时获得所有路径及其对应记录的映射关系表
let { pathList, pathMap } = createRouteMap(routes);
// 需要根据用户的配置做出一个映射表
function match(location) {//通过用户输入的路径获取对应的匹配记录
let record = pathMap[location];
return createRoute(record, {
path:location
})
}
function addRoutes(routes) {//动态添加路由
createRouteMap(routes, pathList, pathMap);
}
return {
match,
addRoutes
}
}
注意这里可能会出现路由嵌套,一旦监测到有路由嵌套,就循环调用递归处理子路由
create-router-map.js
function addRouteRecord(route, pathList, pathMap, parentRecord) {
// 将嵌套路由的父路由与当前路由拼接 路由/a的子路由是/b,那么放入path时就是['/a/b']
let path = parentRecord ? `${parentRecord.path}/${route.path}`: route.path;
let record = {//当前路由产生一个记录
path,
component: route.component,
parent: parentRecord
}
if (!pathMap[path]) {//若映射表中没有当前路径
pathMap[path] = record;
pathList.push(path);
}
// 将子路由也放到对应的pathmap和pathlist,循环递归处理子路由
if (route.children) {
route.foreach(child => {
addRouteRecord(child, pathList, pathMap, record);
})
}
}
// 将所有路径放入字符串,且构造路径记录的映射关系
export default function createRouteMap(routes, oldPathList, oldPathMap) {
let pathList = oldPathList || [];
let pathMap = oldPathMap || {};
// 循环处理当前层路由
routes.forEach(route => {
addRouteRecord(route, pathList, pathMap);
});
return {
pathList,
pathMap
}
}
接下来就来聊聊这个 init 方法,在VueRouter构造函数内部给对象定义了 history 属性,代表的是路由模式,init 函数内部首先拿到了这个属性的值,可以调用该类的方法。因为路由的两种模式又很多同样的API,所以我们可以定义一个父类,然后再让两种模式继承该父类。
父类中构造函数先保存VueRouter实例,同时匹配根路径对应的所有记录。然后定义路由跳转方法。
base.js
export const createRoute = (record, location) => {//根据路径记录匹配到的所有组件
// 存放匹配到的记录
let matched = [];
// 如果record存在就将其及其所有父组件存入数组中,父组件存放位置在前
if (record) {
while (record) {
matched.unshift(record);
record = record.parent;
}
}
return {
...location,
matched
}
}
export default class Base{
// 父类初始化
constructor(router) {
this.router = router;
//搜集 '/' 对应的所有记录
this.current = createRoute(null, {
path: '/'
})
}
// 路由跳转方法
transitionTo(location, complete) {
// 路径变化,currnt更新
let current = this.router.match(location);
// 判断本次要跳转的路径是否和当前路径相同
if (this.current.path === location && this.current.matched.length === current.matched.length) return;
// 用最新的匹配结果更新视图
this.current = current;//这个current只是响应式的,它的变化不会更新_route,所以需要在 init方法中更新
// 调用回调函数更新_route
this.cb && this.cb(current);
}
// 记录传入的回调函数,该回调函数目的在于更新_route
listen(cb) {
this.cb = cb;
}
}
接下来说说 hash 子类和 history 子类,首先 hash 子类做的是很简单,就是定义了两个方法获取当前最新的 hash 值,监听 hash 变化的函数供外部调用,本项目未完善 history 模式子类
hashHistory.js
import History from './base'
// 判断是否有hash值
const ensureSlash = () => {
if (window.location.hash) {
return;
}
window.location.hash = '/';
}
// 继承父类
export default class HashHistory extends History{
constructor(router) {
super(router);
this.router = router;
// 如果使用hash默认如果没有hash应该跳转到首页
ensureSlash();
}
// 获取当前hash值
getCurrentLocation() {
// 得到的hash值带#号所以应该截取一下
return window.location.hash.slice(1);
}
// 定义监听hash变化的函数
setupHashLisener() {
window.addEventListener('hashchange', () => {
// 有变化则跳转路由
this.transitionTo(this.getCurrentLocation())
})
}
}
browsHistory.js
import History from './base'
export default class BrowsHistory extends History {
}
总结
Vue-Router 的实现逻辑还是有点难的,但是只要多看看,再写一遍基本就能理清了。难点就是构建路径和记录的映射关系,因为会出现嵌套路由所以需要递归处理,要点就是 hash 模式和 history 模式以及他们的公共类 base,其中逻辑要多看才能理解。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)