Vue Router 核心原理及实现

前言

本文主要介绍 Vue Router 的核心原理以及如何实现一个简易版本的 Vue Router(history 和 hash 模式)。

前置知识

hashhistory 模式区别

  • 表现形式上:hash 模式 url music.163.com/#/playlist?... , 带有 # 号。

  • 原理区别:hash 模式基于锚点,以及 onhashchange 事件。 history 模式是基于 HTML5 中的 history 模式。history.pushState、replaceState 在 IE10 以后才支持,存在兼容性问题。push 会向服务器发送请求,使用 pushState 不会发送请求,但是会在浏览器端产生历史记录,形成客户端路由。

history 模式使用

  • 该模式需要服务器端的支持。

  • 单页应用中,服务端不存在 www.testurl.com/login,地址会返回 404,提示找不到页面。

  • history 模式下前后端工作过程:history 模式下,刷新页面会向服务器进行网络请求,后端处理 history 模式,需要将默认的 html 文件返回给前端,前端获取到文件后再根据路由自行处理。

Vue Router 使用方式

比较常规的使用方式,避免文章篇幅过长,这里就不一一赘述了。基础步骤如下:

  1. Vue 2.6.11 模板项目中, 安装依赖:
node 复制代码
npm install vue-router
  1. src 目录下创建 router 文件夹,并新建一个 index.js,内容如下:
javascript 复制代码
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../components/Home";
import ComponentA from "../components/ComponentA";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    redirect: "/home",
    component: Home
  },
  {
    path: "/componentA",
    name: "ComponentA",
    component: ComponentA
  },
];

const router = new VueRouter({
  mode: "history",
  routes
});

export default router;
  1. main.js 中引入,在 Vue 实例化时,作为 Options 参数传递给 Vue 的构造函数。
javascript 复制代码
import Vue from "vue";
import App from "./App.vue";
import router from "./router/index";

Vue.config.productionTip = false;

const vm = new Vue({
  router,
  render: (h) => h(App)
}).$mount("#app");

console.log("vm: ", vm);

router 实例对象

Vue 实例化后,router 对象作用在何处? 我们在 main.js 中 Vue 实例化后的位置添加一行打印,看看 Vue 实例化后的对象上有什么。

vm.$options.router 中,我们看到了许多熟悉的对象属性与官方文档上提到的编程式导航方法。

实现 history 模式

CustomRouter

这里我们给自己的 Router 类命名为 CustomRouter,定义如下:

javascript 复制代码
export default class CustomRouter {
  static install(Vue) {}

  constructor(options) {}

  init() {}

  createRouteMap() {}

  initComponents(Vue) {}

  initEvent() {}
}

CustomRouter 类中的各个函数作用依次如下:

  • install 函数,Vue 插件需要对外暴露的 install 方法,关于 Vue 插件开发,可以参考官方文档:cn.vuejs.org/v2/guide/pl...
  • constructor 函数:构造函数。
  • init 函数:全局初始化。
  • createRouteMap:创建路由配置与对应组件的映射,属性值是对应的组件。
  • initComponents:初始化 Router-ViewRouter-Link 组件。
  • initEvent: 监听浏览器 historypopstate 事件,当页面 URL 变化时,将会触发监听事件。

constructor

javascript 复制代码
constructor(options) {
  this.options = options;
  this.routeMap = {};
  this.data = _Vue.observable({
    current: "home"
  });
}

构造函数中的对象成员如下:

  • options: options 中存储的是路由相关的配置。

  • routeMap: 路由 pathcomponent 的映射,是一个对象,对象属性值为 path,对象值为 component,在 createRouteMap 函数中进行初始化。

  • data:通过 2.6.x 版本后提供的 observable api,将对象转换成响应式。

initEvent 函数

监听 popstate 事件,并更新当前的 current。

javascript 复制代码
initEvent() {
  window.addEventListener('popstate', () => {
    this.data.current = window.location.pathname;
  })
}

initComponent

javascript 复制代码
 initComponents(Vue) {
  /**
   * Router-Link 组件
   */
  Vue.component("router-link", {
    props: {
      to: String
    },
    render(h) {
      return h(
        "a",
        {
          // dom 对象属性
          attrs: {
            href: this.to
          },
          on: {
            click: this.clickHandler
          }
        },
        [this.$slots.default]
      );
    },
    methods: {
      clickHandler(e) {
        window.history.pushState({}, "", this.to);
        this.$router.data.current = this.to;
        e.preventDefault();
      }
    }
  });

  /**
   * Router-View 组件
   */
  const self = this;
  Vue.component("router-view", {
    render(h) {
      const component = self.routeMap[self.data.current];
      return h(component);
    }
  })
}

this.data.current 变化时,所有依赖于该变量的组件(router-view)都会重新渲染,这也是为什么要使用 Vue.observable 的原因。

init

分别初始化 routeMapRouter-ViewRouter-Link 组件和 popstate 事件监听器。

javascript 复制代码
init() {
  this.createRouteMap();
  this.initComponents();
  this.initEvent();
}

install

javascript 复制代码
static install(Vue) {
  if (CustomRouter.install.installed) {
    return;
  }

  CustomRouter.install.installed = true;

  _Vue = Vue;

  _Vue.mixin({
    beforeCreate: function () {
      if (this.$options.router) {
        _Vue.prototype.$router = this.$options.router;
        this.$options.router.init();
      }
    }
  });
}

实现 hash 模式

hash 模式整体代码实现上和 history 基本一致,它监听的是 loadhashchange 事件,并在 router-link 组件点击时,设置 window.locationhash 形式。差异代码如下:

javascript 复制代码
Vue.component("router-link", {
  props: {
    to: String
  },
  render(h) {
    return h(
      "a",
      {
        attrs: {
          href: "#" + this.to
        },
        on: {
          click: this.clickHandler
        }
      },
      [this.$slots.default]
    );
  },
  methods: {
    clickHandler(e) {
      window.location.hash = "#" + this.to;
      this.$router.data.current = this.to;
      e.preventDefault();
    }
  }
});

a 标签的 href 修改为了 hash 形式,clickHandler 点击事件处理中,window.location.hash 会被赋值为对应路由的 hash 形式。

hashchangeload 事件监听

javascript 复制代码
onHashChange() {
  if (!window.location.hash) {
    window.location.hash = "#/";
  }
  this.data.current = window.location.hash.substr(1);
}

initEvent() {
  window.addEventListener("load", this.onHashChange.bind(this));
  window.addEventListener("hashchange", this.onHashChange.bind(this));
}

监听 hashchangeload 事件。

代码示例

codesandbox.io/s/build-vue...

参考

相关推荐
恋猫de小郭38 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端