学习《玩转 Vue3 全家桶》之手写 Vue Router

364 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

本文源代码来自极客时间的《玩转 Vue3 全家桶》,对详细内容感兴趣可去官网购买学习

1. 知识储备

1.1 hashchange 事件

Vue Router 默认使用 hash 路由,所以这里实现的代码使用的是 hash 路由。当 URL 中的 hash 值发生变化的时候(即#后面的部分),会触发 hashchange 事件。可以参考 MDN 的介绍。另外,URL 中的 hash 路径也可以通过 window.location.hash 轻松获取。

1.2 Vue3 知识

详细介绍可以查阅官网

  1. ref 可以将原始类型的值转化成响应式对象,该对象仅有一个 value 属性,指向内部的值
  2. defineProps 用于在 <script setup> 中声明 props
  3. component 组件用于渲染动态组件,具体的组件依据 is 后面值进行变化

2. 源码分析

  1. index.js 中包含核心代码实现逻辑,该文件包含 createRouter,createWebHashHistory,useRouter三个方法。

    • createRouter 用于创建 Router 对象,在 Router 类中可以接收1个配置对象,该对象包含 history 和 routes 两个属性,其中 history 就是接收了 createWebHashHistory 方法,用于跟踪当前 url 中 hash 值的变化,而 routes 包含配置路由的对象数组,这个数组包含 path,component 等基本路由信息。另外, Router 类中还包含 install 方法,在创建 Vue 实例的时候调用 use 方法默认会使用该方法,它向全局注入 new Router 对象,另外还创建了 RouterLink 和 RouterView 两个全局组件
    • createWebHashHistory 方法包含 bindEvents 方法和 url 变量,其中 bindEvents 可以用于绑定 hashchange 事件,而 url 用于获取当前 url 的 hash 值。
    • useRouter 方法可在具体的组件中使用,可以获取到 new Router 对象
import { ref, inject } from "vue";
import RouterLink from "./RouterLink.vue";
import RouterView from "./RouterView.vue";

const ROUTER_KEY = "__router__";

function createRouter(options) {
  return new Router(options);
}

function useRouter() {
  return inject(ROUTER_KEY);
}

function createWebHashHistory() {
  function bindEvents(fn) {  // 绑定 hashchange 事件,监听 hash 值的变化
    window.addEventListener("hashchange", fn);
  }
  return {
    bindEvents,
    url: window.location.hash.slice(1) || "/",
  };
}
class Router {
  constructor(options) {
    this.history = options.history;
    this.routes = options.routes;
    this.current = ref(this.history.url);

    this.history.bindEvents(() => {
      this.current.value = window.location.hash.slice(1);  // 通过 Vue3 的 ref,响应式获取当前路由的 hash 值
    });
  }
  install(app) {
      // Vue 实例调用插件的时候,注入 new Router 对象,以及 RouterLink 组件和 RouterView 组件
    app.provide(ROUTER_KEY, this);
    app.component("router-link", RouterLink);
    app.component("router-view", RouterView);
  }
}

export { createRouter, createWebHashHistory, useRouter };
  1. RouterLink.vue 文件实际上就是一个组件。它包含一个 a 链接,可以改变 hash 值,父组件可以通过 to 属性向该组件传值。另外还包含一个默认插槽,用于父组件向子组件中传递内容。
<template>
  <!-- 该组件包含一个 a 标签,点击可切换到对应的 hash 值,另外含有一个默认插槽,用来显示内容 -->
  <a :href="'#' + props.to">
    <slot />
  </a>
</template>

<script setup>
import { defineProps } from "vue";
let props = defineProps({
  to: { type: String, required: true },
});
</script>

  1. RouterView.vue 文件也是一个组件,它通过监听当前 hash 值的变化,来找到 routes 中配置的组件,最后通过动态组件 component 来实现切换效果。
<template>
  <component :is="component"></component>
</template>
<script setup>
import { computed } from "vue";
import { useRouter } from "../grouter/index";
let router = useRouter();
// router.current 是响应式的,所以需要使用 computed。当 router.current 的值发生变化时,遍历数组 router.routes 的每个元素,直到找到和元素中 path 相等的元素,返回这个元素的 component.
const component = computed(() => {
  const route = router.routes.find((route) => route.path === router.current.value);
  return route ? route.component : null;
});
</script>

4. 总结

Vue Router 的核心原理很简单,就是根据 hash 值的变化,匹配对应的组件。另外,在实现的过程中可以发现有很多功能依赖于 Vue3,比如 ref 方法,component 组件等。