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...

参考

相关推荐
想退休的搬砖人1 分钟前
ESLint的简单使用(js,ts,vue)
开发语言·javascript·vue.js
乐多_L5 分钟前
axios的post请求,数据为什么要用qs处理?什么时候不用?
前端·javascript·vue.js
Stanford_11068 分钟前
C++入门基础知识150—【关于C++ 输入/输出运算符重载】
前端·javascript·c++·微信小程序·微信公众平台·twitter·微信开放平台
Domain-zhuo34 分钟前
css3的新特性有哪些?
前端·css·css3
计算机学姐38 分钟前
基于Python的招聘信息推荐系统
开发语言·vue.js·python·mysql·pycharm·django·mvc
qq_316837751 小时前
html 图片转svg 并使用svg路径来裁剪html元素
前端·javascript·html
就是我1 小时前
来谈谈JSON.stringify
前端·javascript·面试
池鱼ipou1 小时前
前端搞AI:探秘brain.js !!!
前端·人工智能
发现你走远了1 小时前
『VUE』30. 生命周期的介绍(详细图文注释)
前端·javascript·vue.js
Dark_programmer1 小时前
antd-vue - - - - - a-input-password聚焦外发光样式取消
前端·javascript·vue.js