让面试官为你停留 —— Vue 应用中如何实现简易版的 Vue-Router(上篇)
(一)介绍
在前端开发中,路由管理是维持页面状态、导航和组件渲染的核心功能之一。虽然 Vue.js 生态系统中 Vue Router 是处理路由的官方库,但理解其底层原理是提高开发技能的重要步骤。在这篇文章中,我们将逐步构建一个简化版的 Vue 路由器(grouter),帮助读者更深入地理解路由机制。
实现思路:
1. 规划路由器核心功能
- 路由注册与初始化
- 路由响应与视图更新
- History管理(我们选择了hash模式进行实现)
2. 设计组件接口
RouterLink:处理导航链接。RouterView:根据当前路由动态渲染对应组件。
3. 开发History管理器
- 封装变更URL和监听URL变化的功能。
4. 实现Router类
- 管理路由表。
- 监听URL变化并更新视图。
5. 集成到Vue中
- 提供插件安装方法,使用
app.use(router)。
接下来,让我们开启自定义 Vue 路由器的实现吧。
(二)项目结构与主要文件
在这个示例中,我们构建了一个简化版的Vue-Router,包括以下几个关键文件:
grouter/index.js- 定义和创建路由实例。App.vue- 根组件,展示如何使用路由链接与视图。main.js- Vue应用入口文件,初始化Vue应用并使用路由。RouterLink.vue与RouterView.vue- 表现层组件,分别对应路由的导航与显示。router/grouter/index.js- 手写的简易路由核心逻辑。
(三)导入关键组件 和 Vue API工具
router\grouter\index.js 页面
import RouterLink from "./RouterLink.vue"
import RouterView from "./RouterView.vue"
import { ref,inject } from 'vue'
(四)前期准备工作
router\grouter\index.js 页面
const ROUTER_KEY = '__router__'
// 在任何地方,就可以拿到 router 对象(简洁方便)
const useRouter = () => {
return inject(ROUTER_KEY)
}
// 封装 VueRouter
const createRouter = (options) => {
return new Router(options)
}
1. Composition API
useRouter函数:这是一个组合式函数,它使用了Vue的inject函数。inject允许组件从父组件或祖先组件中注入一个值,而无需显式地通过props传递。在这个例子中,useRouter函数返回了注入的ROUTER_KEY对应的值,即Vue Router的实例。
2. 依赖注入(Dependency Injection)
依赖注入是一种设计模式,用于管理组件之间的依赖关系。在Vue中,依赖注入是通过provide和injectAPI实现的:
ROUTER_KEY常量:定义了一个依赖注入的键,用于在组件树中标识Vue Router的实例。inject函数:在useRouter函数中使用,从组件的上下文中检索与ROUTER_KEY相关联的值。
3. 类的实例化
createRouter函数:这个函数用于创建和返回一个新的Router实例。在Vue Router中,Router类负责管理路由规则、历史记录等。createRouter函数接收一个配置对象options,并使用这些选项来创建一个具体的Router实例。
(五)创建 Router 实例
router\grouter\index.js 页面
首先,我们需要定义 Router 类与 createRouter 函数。Router 类负责处理路由的安装和当前路由的管理,而 createRouter 是用来实例化路由对象的工厂函数:
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);
});
}
install(app) {
app.provide('__router__', this);
app.component('router-link', RouterLink);
app.component('router-view', RouterView);
}
}
1. 使用 Composition API 进行状态管理
ref的使用:ref是 Vue 3 Composition API 的一部分,用于创建响应式引用。在这段代码中,ref用于创建一个响应式的current属性,它将根据 URL 的 hash 变化而自动更新。这是 MVVM 模式中“ViewModel”层的一部分,它负责将 Model(即 URL hash)的变化反映到 View 中。
2. 事件监听与响应
bindEvents方法的调用:通过this.history.bindEvents方法,代码注册了一个事件监听器,监听hashchange事件。当 URL 的 hash 发生变化时,事件监听器将被触发,更新current的值。这展示了如何在 Vue.js 中处理外部事件并保持状态的响应式更新。
3. Vue Router 的核心概念
-
路由规则:
this.routes存储了路由规则,这些规则将被用于决定在给定的 URL 下应该显示哪个组件。这是 Vue Router 的核心功能之一。 -
历史管理:
this.history对象负责管理浏览器的历史记录,包括 URL 的变化。Vue Router 支持两种历史模式:hash 和 history。这里使用的是 hash 模式。
4. Vue 应用的扩展与组件注册
-
app.provide的使用:通过app.provide方法,将Router实例注入到 Vue 应用的上下文中。这使得任何组件都可以通过inject接收这个路由器实例,从而访问其功能,如导航和路由信息。 -
全局组件注册:
app.component方法用于注册全局组件。在这段代码中,注册了router-link和router-view组件,它们是 Vue Router 中用于创建导航链接和渲染路由组件的关键组件。
(六)定义 Hash 历史
router\grouter\index.js 页面
以下这段代码定义了一个函数 createWebHashHistory,用于创建一个基于浏览器 URL hash 的历史管理器。这个管理器主要用于处理和监控 URL hash 的变化,路由的 hash 模式利用了 URL 中的 hash(井号标记)来模拟完整的 URL 路径,这对于构建使用 hash 模式的历史跟踪的单页应用(SPA)是非常重要的。
const createWebHashHistory = () => {
function bindEvents(fn) {
window.addEventListener('hashchange', fn);
}
return {
url: window.location.hash.slice(1) || '/',
bindEvents
};
}
1. 模块化与封装
- 这段代码定义了一个
createWebHashHistory函数,该函数返回一个对象,封装了与浏览器 URL hash 相关的操作。这种封装使得代码更易于管理和复用,同时也隐藏了实现细节,提供了清晰的接口。
2. 事件监听
bindEvents函数用于绑定hashchange事件。这个事件在 URL 的 hash 部分发生改变时触发。通过使用window.addEventListener方法,代码能够监听到这个事件,并执行传入的回调函数fn。这展示了如何在 JavaScript 中处理事件,以及如何将事件处理逻辑与其它业务逻辑分离。
3. 对象字面量与方法暴露
-
返回的对象包含两个属性:
url和bindEvents方法。url属性获取当前 URL 的 hash 部分(如果存在),并将其作为字符串返回,如果没有 hash,则返回'/'。这展示了如何从window.location对象中提取信息。bindEvents方法则被直接定义为对象的一个属性,它允许外部代码通过这个对象来绑定hashchange事件的监听器。
4. 字符串操作
- 使用
window.location.hash.slice(1)来获取 URL 的 hash 部分。window.location.hash:这返回当前 URL 的 hash 部分,也就是 URL 中#符号后面的部分。.slice(1):这个方法从字符串的第二个字符开始截取,直到字符串结束。在这个上下文中,它会移除 hash 部分的开头#字符,留下/home。
|| '/':这是一个逻辑或运算符。如果window.location.hash.slice(1)返回的是一个非空字符串,那么这个表达式将返回这个字符串。但如果它是空字符串(意味着 URL 没有 hash 部分),那么它将返回'/'。这确保了即使没有 hash 部分,应用也能有一个默认的路由路径。
(七)总结
如此以来,我们就完成了手写Vue-Router中最核心的板块 —— 手写简易路由核心逻辑。
这段代码实现了基本的 Vue Router 功能,包括创建 Router 实例、监听 URL hash 变化、定义路由规则、提供 Router 实例给 Vue 应用、以及注册必要的组件。通过使用 Composition API 和 Vue Router 的核心组件,这段代码允许你创建和管理单页应用中的路由。
(八)源码
router/grouter/index.js
import RouterLink from "./RouterLink.vue"
import RouterView from "./RouterView.vue"
import { ref,inject } from 'vue'
const ROUTER_KEY = '__router__'
// 在任何地方,就可以拿到 router 对象(简洁方便)
const useRouter = () => {
return inject(ROUTER_KEY)
}
// 封装 VueRouter
const createRouter = (options) => {
return new Router(options)
}
// 返回一个 hash 路由对象
// url #/about
// hashChange
const createWebHashHistory = () => {
function bindEvents(fn) {
window.addEventListener('hashchange', fn)
}
return {
url: window.location.hash.slice(1) || '/',
bindEvents
}
}
class Router {
constructor(options) {
this.history = options.history
this.routes = options.routes
console.log(options, '////');
// 当前的 url 状态,是 router-view component 计算属性的依赖
this.current = ref(this.history.url)
this.history.bindEvents(() => {
// this 指向 Router
this.current.value = window.location.hash.slice(1)
console.log(this.current.value);
})
}
install(app) {
console.log(app);
// console.log('vue 要对接 vue-router');
// 全局组件的声明
// 全局提供 router对象
app.provide(ROUTER_KEY,this)
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
}
}
export {
createRouter,
createWebHashHistory,
useRouter
}