VueRouter 原理实现- 笔记

155 阅读2分钟

Vue 生命周期

Vue 语法和概念

  • 差值表达式
  • 指令(14个)
  • 计算属性和侦听器
  • class 和 style 绑定
  • 条件渲染和列表渲染
  • 表单输入渲染
  • 组件
  • 插槽
  • 插件
  • 混入 mixin
  • 深入响应式原理
  • 不同构建版本的 vue

vue router 原理实现

基本使用

import Vue from "vue";
import VueRouter from "vue-router";
import Index from "./views/Index.vue";

Vue.use(VueRouter);

const routes = [
    {
        path: "/",
        name: "Index",
        component: Index,
    },
    {
        path: "/blog",
        name: "Blog",
        // route level code-splitting
        // lazy-loaded
        component: import(/* webpackChunkName: "blog" */'./views/Blog.vue'),
    }
];

const router = new VueRouter({
    routes
});

new Vue({
    router,
    el: "#app"
});


// Index.vue
<template>
    <router-view></router-view>
</template>

router 会给组件注入 $route$router 两个属性。

动态路由匹配

const routes = [
    {
        path: "/blog/:id",
        name: "blog",
        // 路由参数 id 会传入组件中
        props: true,
        component: Blog
    }
]

嵌套路由

const routes = [
    {
        path: "/",
        component: Layout,
        children: [
            {
                path:"",
                name:"index",
                component: Index
            }
        ]
    }
]

Blog.vue

<script>
export default {
    name: "blog",
    props: ["id"]
}

</script>

编程式导航

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

Hash 模式和 History 模式的区别

  • Hash 模式是基于锚点,以及 onhashchange 事件
  • History 模式是基于 html5 中的 history API
    • history.pushState() IE10 以后才支持
    • history.replaceState()

History 模式的使用

  • History 需要服务器的支持 history 模式下, 如果浏览器刷新,会向服务器发起请求,如果服务器没有配置的话,就可能返回 404。

History 模式 - nodejs

const path = require("path")
const history = require("connect-history-api-fallback")
const express = require("express");

const app = express();

app.use(history)
app.use(express.static(path.join(__dirname, "./public"));

app.listen(3000, () => {
    console.log("服务器开启,端口: 3000")
})

History 模式 - nginx

start nginx
nginx -s reload

nginx -s stop

http {

    location / {
        root html;
        index index.html index.htm
        try_files $uri $uri/ /index.html
    }
}

VueRouter History 模式下的实现原理

VueRouter 类图

VueRouter类图.png

let _Vue = null;

export default class VueRouter {
  static install(Vue) {
    // 1. 判断当前插件是否已经被安装
    if (VueRouter.install.installed) {
      return;
    }
    VueRouter.install.installed = true;

    // 2,把 Vue 构造函数记录到全局变量
    _Vue = Vue;
    // 3. 把创建 Vue 实例时传入的 routes 对象注入到 Vue 实例上
    // 混入
    _Vue.mixin({
      beforeCreate() {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router;
          this.$options.router.init();
        }
      },
    });
  }

  constructor(options) {
    this.options = options;
    this.routeMap = {};
    // 创建响应式对象
    this.data = _Vue.observable({
      current: "/",
    });
  }

  init() {
    this.createRouteMap();
    this.initComponents(_Vue);
    this.initEvent();
  }

  createRouteMap() {
    // 遍历所有的路由规则, 把路由规则解析成键值对的形式 存储到 routeMap 中
    this.options.routes.forEach((route) => {
      this.routeMap[router.path] = router.component;
    });
  }

  initComponents(Vue) {
    const self = this;
    Vue.component("router-link", {
      props: {
        to: String,
      },
      // runtime 版本的 vue 不支持 template 模版
      //   template: '<a :href="to"><slot></slot></a>',
      render(h) {
        return h(
          "a",
          {
            attrs: {
              href: this.to,
            },
            on: {
              click: this.clickHandler,
            },
          },
          [this.$slote.default]
        );
      },
      methods: {
        clickHandler(e) {
          history.pushState({}, "", this.to);
          // 修改后会触发组件的重新渲染
          this.$router.data.current = this.to;
          e.prevenDefault();
        },
      },
    });

    Vue.component("router-view", {
      render(h) {
        // 当前路由对应的组件
        const component = self.routeMap[self.data.current];
        return h(component);
      },
    });
  }

  initEvent() {
    // 前进 后退,历史记录被激活时,会触发 popstate 事件
    window.addEventListener("popstate", () => {
      this.data.current = window.location.pathname;
    });
  }
}