Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
本文源代码来自极客时间的《玩转 Vue3 全家桶》,对详细内容感兴趣可去官网购买学习
1. 知识储备
1.1 hashchange 事件
Vue Router 默认使用 hash 路由,所以这里实现的代码使用的是 hash 路由。当 URL 中的 hash 值发生变化的时候(即#后面的部分),会触发 hashchange 事件。可以参考 MDN 的介绍。另外,URL 中的 hash 路径也可以通过 window.location.hash 轻松获取。
1.2 Vue3 知识
详细介绍可以查阅官网
- ref 可以将原始类型的值转化成响应式对象,该对象仅有一个 value 属性,指向内部的值
- defineProps 用于在
<script setup>中声明 props - component 组件用于渲染动态组件,具体的组件依据 is 后面值进行变化
2. 源码分析
-
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 };
- 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>
- 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 组件等。