前言
在构建 Vue 项目的过程中,vue-router
是不可或缺的核心工具之一,它为我们带来了便捷的路由管理和页面导航功能。通过<router-link />
和<router-view />
,我们可以轻松地在组件之间跳转,构建单页面应用的基础架构。虽然 Vue 提供了强大的工具和插件生态,但如果你想深入了解它们的工作原理,动手实现一个简单的vue-router
,无疑是一个非常好的学习途径。在本文中,我们将从头开始,手写一个简化版的 vue-router
,带你了解其核心机制,并通过这一过程加深对 Vue 框架的理解。无论你是初学者还是有经验的开发者,希望这篇文章能为你提供有价值的见解。
全局组件
当我们的项目引入router时,我们不需要引入<router-link />
和<router-view />
就可以在任何地方直接访问,因为它们是全局组件。可以使用app.component()
方法让我们自己写的组件变为全局组件去使用。手写vue-router
,需要我们自己去自定义组件,当然如果我们没有引入组件,dom
会把它当作一般标签来解析。
use 方法做了什么事?
在 Vue 的架构体系中,Vue 本身主要专注于构建组件的核心思想,致力于打造诸如 MVVM 模式和响应式机制等关键特性。然而,为了提供更全面、更丰富的功能,Vue 将许多扩展功能的实现交给了其繁荣的生态系统,并以开源的方式共同发展。
其中,vue-router
作为 Vue 生态中的重要路由模块,承担着页面导航和路由管理的关键职责。而 Vue 与它所属生态系统中的各个模块进行对接和整合的关键桥梁,正是这个use
方法。
通过use
方法,Vue 能够灵活地引入和集成来自生态系统的各种功能模块,实现功能的扩展和增强,为开发者提供了更加便捷、高效的开发体验,共同构建出强大且多样化的应用。
src目录结构
基础配置
使用原来写法写路由配置,我们自己去手写createRouter
和createWebHashHistory
。
javaScript
import { createRouter, createWebHashHistory } from './grouter/index';
import Home from '../pages/Home.vue';
import About from '../pages/About.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
About.vue和Home.vue就简单的写一些基础代码,能显示基本的信息即可。
Vue
<template>
<div>About</div>
</template>
<template>
<div>Home</div>
</template>
App.vue写上<router-view />
和<router-link />
组件,同时要手写这两个组件,实现vue-router
的效果。
Vue
<template>
<header>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>
</header>
<main>
<router-view></router-view>
</main>
<footer></footer>
</template>
手写源码
最重要的手写源码放在grouter
文件夹下,我们引入的router就在这自己手写。
1. index.js
javaScript
import RouterLink from './RouterLink.vue';
import RouterView from './RouterView.vue';
import { ref, inject } from 'vue';
// 单例的责任
export const createRouter = (options) => {
return new Router(options);
};
export const createWebHashHistory = () => {
function bindEvents(fn) {
window.addEventListener('hashchange', fn);
}
// history 对象
return {
url: window.location.hash.slice(1) || '/',
bindEvents,
};
};
// 标记一下 router 要向全世界暴露
// 常量配置项 全部大写
const ROUTER_KEY = '__router__';
// use 开头的是一派 hooks 函数式编程
export const useRouter = () => {
return inject(ROUTER_KEY);
};
class Router {
constructor(options) {
this.history = options.history;
this.routes = options.routes;
this.current = ref(this.history.url);
this.history.bindEvents(() => {
// console.log('////////');
this.current.value = window.location.hash.slice(1);
});
}
// use 调用 插件install方法
install(app) {
// 全局声明有一个router 全局使用的对象
app.provide(ROUTER_KEY, this);
console.log('准备与vue 对接', app);
app.component('router-link', RouterLink);
app.component('router-view', RouterView);
}
}
createRouter
是一个工厂函数,用于创建一个 Router
实例。它接受一个 options
参数,其中包含了路由配置。
createWebHashHistory
是一个用于创建基于 hash 的历史记录管理器的函数。它返回一个对象,该对象包含了当前 URL(去掉了 #
的部分)和一个 bindEvents
方法,用于监听 hashchange
事件。这种模式使用 URL 的 #
部分作为路径,从而实现无刷新路由。
useRouter
是一个函数式编程的 Hook,用于在 Vue 组件中访问路由器实例。它使用 Vue 的 inject
函数,通过 ROUTER_KEY
从依赖注入系统中获取路由器实例。
路由Router
中的constuctor
:
this.history
:存储路由历史管理对象,控制路由状态。this.routes
:存储路由配置。this.current
:使用 Vue 的ref
创建一个响应式变量,用于存储当前的路径。this.history.bindEvents()
:绑定一个事件监听器,当 URL 中的 hash 值发生变化时,更新this.current
的值。
install
方法是 Vue 插件模式的一部分。当通过 app.use(router)
安装路由器插件时,Vue 会调用这个方法。
app.provide(ROUTER_KEY, this)
:通过provide
方法向 Vue 的依赖注入系统中注册路由器实例,以便在应用中的任何组件中都能通过inject
访问。app.component('router-link', RouterLink)
和app.component('router-view', RouterView)
:注册了两个全局组件RouterLink
和RouterView
,分别用于导航和显示当前路由对应的组件。
插槽
如何把组件中间的文字传给RouterLink
组件呢?我们使用插槽,组件到底显示什么东西,交给外部去自定义,提升了组件的可复用性,不用props
,提升组件的定制性。
2. RouterLink:
Vue
<template>
<a :href="'#' + props.to">
<slot></slot>
</a>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
to: {
type: String,
required: true,
},
});
</script>
- 定义了一个
<a>
链接标签,来实现router-link
的跳转功能,:href
属性加上:
变成动态值。props.to
是组件的一个属性,表示目标路由路径。'#' + props.to
表示这个链接的href
值是一个 hash 值。 <slot>
是一个插槽,用于在使用这个组件时插入自定义的内容。插槽的内容将显示在<a>
标签内。
3. RouterView:
Vue
<template>
<component :is="component" />
</template>
<script setup>
import { useRouter } from './index.js';
import { computed } from 'vue';
const router = useRouter();
console.log(router);
// ref 处理私有数据状态
// router-view 动态组件展示 依赖于url的变化 需要重新计算
// 响应式 router.current 设置为ref
const component = computed(() => {
const route = router.routes.find(
(route) => route.path == router.current.value
);
console.log('/////');
return route ? route.component : null;
});
</script>
<component>
是 Vue 提供的动态组件,可以根据 :is
绑定的值来渲染不同的组件。computed
计算属性,因为router-view
需要动态展示内容,依赖于url的变化,所以需要重新计算,我们就用computed
属性。
结语
通过这次手写 vue-router
的练习,我们不仅对 Vue 的路由机制有了更深入的理解,也体验了 Vue 插件的强大扩展性。从定义全局组件到实现动态组件渲染,再到利用插槽提高组件的复用性,每一个步骤都让我们更好地掌握了 Vue 的核心思想。尽管我们实现的是一个简化版的 vue-router
,但它包含的原理和逻辑却是我们在日常开发中不可忽视的基础。希望通过这篇文章,能激发你对 Vue 更大的兴趣,并帮助你在前端开发的道路上走得更远。无论是日后的项目开发,还是面对复杂的业务需求,掌握这些底层知识,都将是你不可或缺的技能储备。