手写 Vue Router,揭秘路由背后的魔法!🔮
前言:从使用者到创造者的转变
大家好!在日常的 Vue 开发中,我们经常使用 Vue Router 来管理路由。但你是否曾好奇过,这个看似神奇的 router 到底是如何工作的?今天,就让我们一起揭开 Vue Router 的神秘面纱,从零开始手写一个简易版的路由器!
回顾:Vue Router 的基本使用
在开始造轮子之前,让我们先回忆一下 Vue Router 的标准用法:
1. 全局注册路由
js
// main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router) // 这一步很关键!
.mount('#app')
🤔 思考时刻 :这个 .use(router)
到底做了什么魔法?
2. 创建路由实例
js
// router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
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(), // 创建 hash 模式
routes
})
export default router
3. 在组件中使用路由
vue
<!-- App.vue -->
<template>
<div>
<div>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
🎯 观察发现 :<router-link>
和 <router-view>
这两个组件就像是凭空出现的魔法道具!
揭秘时刻:Vue Router 的核心构成
揭秘 app.use(router)
的魔法!🔮
当我们写下这行看似简单的代码时:
js
createApp(App).use(router).mount('#app')
实际上发生了一系列精妙的"魔法"。让我为你一步步揭开这个神秘面纱!
1. app.use()
是什么?
app.use()
是 Vue 3 的插件系统的核心方法。它的作用是:
安装一个插件,扩展 Vue 应用的功能
就像给你的应用安装"扩展程序"一样!📦
2. app.use()
的工作原理
当调用 app.use(router)
时,Vue 内部会执行以下检查:
js
// 伪代码:Vue 内部的 use 方法逻辑
use(plugin, options) {
if (plugin && typeof plugin.install === 'function') {
// 情况1:插件有 install 方法
plugin.install(app, options)
} else if (typeof plugin === 'function') {
// 情况2:插件本身是个函数
plugin(app, options)
} else {
console.warn('无效的插件')
}
}
在我们的路由场景中,router
对象有一个 install
方法,所以会走第一种情况!
3. 路由的 install
方法做了什么?
让我们回顾手写路由中的 install
方法:
js
class Router {
// ... 其他代码
install(app) {
// 魔法1:建立全局"秘密通道"
app.provide(ROUTER_KEY, this)
// 魔法2:注册全局组件
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
console.log('🎉 路由安装成功!', app)
}
}
魔法1:app.provide(ROUTER_KEY, this)
这行代码创建了一个依赖注入系统:
js
// 提供者(在路由安装时)
app.provide(ROUTER_KEY, this) // this 就是路由实例
// 消费者(在任何组件中)
const router = inject(ROUTER_KEY) // 获取路由实例
想象一下,这就像在应用中建立了一条"秘密地铁线路"🚇:
- 起点站 :
app.provide()
把路由实例放进"地铁" - 任意站点 :任何组件都可以通过
inject()
上车,获取路由实例
这样就不需要通过 props 一层层传递了!避免了"props 钻地洞"的麻烦。
魔法2:注册全局组件
js
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
这相当于向 Vue 声明:
"亲爱的 Vue,从现在开始,无论在哪个组件中看到
<router-link>
或<router-view>
,请使用我提供的这两个组件来渲染它们!"
就像给 Vue 的"词汇表"里添加了两个新单词!📚
4. 为什么需要这个魔法?
没有 app.use(router)
的话:
- 组件不认识 :Vue 看到
<router-link>
会一脸懵逼🤔 - 实例无法共享:每个组件都要手动接收路由实例
- 代码重复:在每个组件都要导入和使用路由
有了 app.use(router)
:
- 开箱即用:全局组件随处可用 🎁
- 轻松访问:任何组件都能获取路由实例
- 维护简单:路由逻辑集中管理
总结
app.use(router)
的魔法其实就是:
一个标准化的插件安装协议,让路由能够:
- 注册全局组件 - 让 Vue 认识
<router-link>
和<router-view>
- 建立依赖注入 - 让所有组件都能轻松访问路由实例
- 执行初始化逻辑 - 为路由功能做准备
这就像给 Vue 应用安装了一个"路由操作系统",让整个应用具备了页面导航和组件切换的能力!💫
现在,让我们思考一个问题:如果要手写 Vue Router,我们需要实现什么?
从 Vue Router 的 API 文档中,我们可以看到路由实例包含了很多方法:

但今天,我们重点关注三个核心部分:
createRouter
- 创建路由实例createWebHashHistory
- 创建 hash 模式- 路由实例的
install
方法
手写 Vue Router 实战 🛠️
让我们开始动手实现自己的路由吧!
第一步:创建路由核心文件
js
// grouter/index.js
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)
}
// 创建 hash 模式(简化版)
function createWebHashHistory() {
// 这里先留空,后续完善
return {}
}
// 在组件中获取路由实例的 hook
function useRouter() {
return inject(ROUTER_KEY)
}
// 路由类 - 核心大脑
class Router {
constructor(options) {
this.routes = options.routes // 存储路由配置
// 这里还可以添加更多功能,比如当前路由状态、导航守卫等
}
// 必须的 install 方法 - 这是 app.use(router) 时调用的!
install(app) {
// 1. 提供路由实例给所有组件
app.provide(ROUTER_KEY, this)
// 2. 注册全局组件
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
console.log('🎉 路由安装成功!', app)
}
}
export { createRouter, createWebHashHistory, useRouter }
第二步:实现 RouterLink 组件
vue
<!-- grouter/RouterLink.vue -->
<template>
<a :href="'#' + props.to">
<!-- :href="'#'+to" 表示动态的 hash 链接 -->
<slot />
<!-- slot 插槽:让使用者可以自定义链接内容 -->
</a>
</template>
<script setup>
import { defineProps } from 'vue'
// 定义组件 props
let props = defineProps({
to: {
type: String,
default: '/'
}
})
</script>
<style scoped>
/* 可以在这里添加链接样式 */
</style>
💡 知识点 :<slot />
就像是一个占位符,允许使用者插入自定义内容,比如:
vue
<router-link to="/about">
🚀 关于我们 <!-- 这个内容会替换 <slot /> -->
</router-link>
第三步:实现 RouterView 组件
vue
<!-- grouter/RouterView.vue -->
<template>
<div>
<!-- component 是 Vue 的动态组件 -->
<!-- :is="component" 根据计算属性动态决定渲染哪个组件 -->
<component :is="component" />
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRouter } from '../grouter/index'
// 获取路由实例
let router = useRouter()
// 计算属性:根据当前 hash 找到对应的组件
const component = computed(() => {
// window.location.hash 获取当前 hash(去掉 # 号)
const currentHash = window.location.hash.slice(1) || '/'
// 在路由配置中查找匹配的组件
const route = router.routes.find(item => item.path === currentHash)
return route ? route.component : null
})
</script>
<style scoped>
/* 视图容器样式 */
</style>
🎯 核心逻辑 :RouterView
就像是一个智能的组件放映机,根据 URL 的变化自动切换显示的组件!
深入理解关键概念 🧠
1. app.use()
的魔法
当我们调用 app.use(router)
时,Vue 会:
- 检查
router
是否有install
方法 - 如果有,就调用
router.install(app, ...options)
- 这就是插件机制的奥秘!
2. 依赖注入:provide
和 inject
js
// 在路由安装时提供实例
app.provide(ROUTER_KEY, this)
// 在任意组件中获取路由实例
const router = inject(ROUTER_KEY)
这就像建立了一条"秘密通道",让所有组件都能访问到路由实例,而不需要通过 props 层层传递!
3. 全局组件注册
js
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
这相当于告诉 Vue:"嘿,以后在任何地方看到 <router-link>
和 <router-view>
,就用我提供的这些组件来渲染!"
使用我们手写的路由
现在,我们可以像使用官方 Vue Router 一样使用我们自己的路由了!
js
// router/index.js - 改用我们的手写路由
import { createRouter, createWebHashHistory } from './grouter/index'
// ... 其他代码保持不变
// main.js - 使用方式完全一样!
import router from './router'
app.use(router)
效果展示 ✨


总结与展望 🚀
通过这个简易的路由实现,我们学到了:
- 插件机制 :Vue 的
app.use()
如何工作 - 依赖注入 :如何使用
provide/inject
共享状态 - 全局组件:如何注册全局可用的组件
- 动态组件 :Vue 的
<component :is>
的妙用 - 路由原理:hash 路由的基本工作原理
当然,这只是一个基础版本。完整的 Vue Router 还包括:
- 历史记录管理(HTML5 History API)
- 路由参数解析
- 导航守卫
- 路由嵌套
- 懒加载等高级功能
但最重要的是,我们现在理解了路由的核心原理!下次使用 Vue Router 时,你会更加清楚背后发生的事情。
希望这篇文章对你有所帮助!如果有任何问题,欢迎在评论区讨论~ 💬
编程的乐趣不仅在于使用工具,更在于理解工具背后的原理! 🎉
PS:想要挑战自己的话,可以尝试实现路由参数解析或导航守卫功能哦!