IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue-Router源码学习及手撕代码 -> 正文阅读

[JavaScript知识库]Vue-Router源码学习及手撕代码

vue-router源码解析及手撕代码

项目环境

本项目有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方法 :
    1. 会注册一些全局组件
    2. 调用 Vue.mixin() 方法使每个组件都可以获取 router 属性,这里是重点难点,具体看代码注释。
    3. 在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类
    1. 在构造函数中定义matcher属性,它是一个函数,此函数有两个功能,匹配路径对应的记录,添加新的路由,在该函数首先会先调用 createRouteMap 方法创建路径与记录的映射关系。创建历史管理,选择路由的模式hash还是history。
    2. 定义 match 方法,返回路径对应的记录
    3. 定义 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,其中逻辑要多看才能理解。
在这里插入图片描述

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-14 09:51:45  更:2022-05-14 09:53:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:58:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码