手写vue的路由功能,深入理解路由源码
路由简介
在 Vue.js 的开发中,经常被提及的“三大法宝”是指构建 Vue 应用程序时最常用到的三个核心部分。虽然这个说法非官方,但它广泛存在于社区中,用来描述开发 Vue 复杂应用时的核心工具集。以下是 Vue 的三大法宝:
- 组件系统 (Component System) : Vue 的组件系统是其核心特性之一,它允许开发者将 UI 分解为独立、可重用的组件。每个组件都有自己的模板、逻辑和样式,可以独立地开发和测试。组件的复用和组合能力极大地提高了开发效率和代码的可维护性。
- Vue Router: Vue Router 是 Vue.js 的官方路由管理器,用于处理单页面应用程序中的导航和视图切换。它提供了一种将 URL 映射到特定组件的方法,并且可以管理嵌套路由和复杂的导航逻辑。Vue Router 还支持过渡效果、懒加载组件和导航守卫等功能,使开发者能够创建流畅的用户体验。
- 状态管理库 (State Management Library) : 在 Vue.js 中,最常用的状态管理库是 Vuex,它提供了一个集中式存储管理应用所有组件的状态,并以 map 的方式提供更方便的访问这些状态。Vuex 支持状态的订阅、更改、模块化组织和时间旅行调试等功能。对于 Vue 3,Pinia 成为了另一种流行的选择,它提供了更现代和简洁的 API,与 Composition API 配合得更好,并且具有更好的 TypeScript 支持。
对于路由的使用,我相信大家都是信手拈来的,今天我们不聊它的使用,而是手搓一个简单的Vue Router,让我们一起走入vue路由源码的分析。
手搓路由源码
对于手搓一个路由功能,我们首先需要了解,路由的工作原理,它是怎么一步一步被用于我们的项目中的,且它给我们带来了什么效果。
初始化和安装
当使用 Vue Router 时,首先需要在 Vue 应用中安装它。这通常通过 Vue.use(VueRouter)
来完成。
那么vue.use函数又做了什么事呢?
实际上,use是在为vue项目安装
插件
,像我们使用vue-router
时需要下载,然后全局引入后才可以使用其内部的组件,它们都不是vue的内置组件,内置组件不需要从外界引入,这样需要外界引入文件才可以使用的功能,我们称之为外部插件。
根据vue官方文档解释:插件可以是一个带 install()
方法的对象,亦或直接是一个将被用作 install()
方法的函数。插件选项 (app.use()
的第二个参数) 将会传递给插件的 install()
方法。 所以router它是一个拥有install
方法的实例对象。
通过观察router
文件下的index.js
和查看vue-router官方文档,createRouter
方法返回了一个router实例对象。且当我们在全局使用app.use(router)
时,实例对象的install方法会被自动调用。
在 Vue Router 的 install
方法中,它会注册全局的 <router-link>
和 <router-view>
组件。这也全局就可以识别这两个组件了。
在Router对象的install
方法内声明全局组件
插件可以利用 app
参数来访问和扩展 Vue 应用实例。当你调用 app.use(plugin)
时,Vue 会调用插件的 install
方法,并将 Vue 应用实例作为参数传递给它。这个 app
参数实际上是一个 Vue 应用的上下文,它允许插件直接与应用交互,添加全局方法、混入、组件、指令等。
//js部分导入 router-link,和 router-view 组件
import RouterLink from './RouterLink.vue'
// 在install方法内加入
app.component('router-link', RouterLink)
// 用同样的方法声明全局router-view 组件
app.component('router-view', RouterView)
那么这两个组件怎么做呢?
router-link
主要是对a标签进行封装,实现url地址的改变。它必须指定to属性跳转的路径。也就是这样的,需要给组件传入一个名字为to
的参数:
<!-- router-link组件 -->
<template>
<a :href="'#' + props.to">
<slot />
</a>
</template>
<script setup>
const props = defineProps({
to: {
type: String,
required: true
}
})
</script>
<style lang="scss" scoped>
</style>
至于为什么拼接一个#, 是因为vue-router的路由模式是hash模式,所以需要加上#,方便可以使用hashchange事件监听。
现在你的项目就可以识别router-link
组件了,目前可以实现url地址的跳转,router-view组件的代码我们等完善方法后再说,那么我们继续完善吧。
完善createWebHashHistory方法
我们暂时实现这个兼容性较强的方法吧,实际上还有一个createWebHistory方法需要实现,先做个简单的吧。
在createWebHashHistory
方法内,它主要返回一个对象,赋值给history
属性,而这个history
属性将会被当作参数传入createRouter
。
export const createWebHashHistory = () =>{
function bindEvents(fn) {
window.addEventListener('hashchange',fn)
}
// history 对象 该对象主要记录一个url的哈希路径,和一个函数
return {
url: window.location.hash.slice(1) || '/',
bindEvents,
}
}
这个fn函数,是用作事件监听的回调函数,也就是当hashchange
事件发生时被调用。
createRouter()传参数
很清楚,我们在创建路由实例时,都会传入一个参数,它的history
属性记录着上一次的url哈希路径,携带一个函数,当路径发生改变时会被调用。还有一个配置着所有路由路径的数组routes
。
它们主要用于做什么呢?
对路由进行初始化:
在Router的构造函数constructor内进行路由的初始化,把参数的数据都挂载到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) || '/'
})
}
router的current属性,始终记录着当前对应的url哈希路径,当url地址被修改时,触发hashchange
事件,启动回调函数,修改router实例的current属性的值。
useRouter()方法
经常做项目的小伙伴都会发现,我们在子组件内总是使用这个useRouter方法就获取了当前使用的router实例对象,其实这里主要有两个api的使用。
那就是provide函数和inject函数,这个很像localStorage
的getItem
和setItem
方法,一方存储,一方获取。
-
示例
import { createApp } from 'vue' const app = createApp(/* ... */) app.provide('message', 'hello')
在应用的某个组件中:
import { inject } from 'vue' export default { setup() { console.log(inject('message')) // 'hello' } }
最后的router-view组件
router-view组件是路由的关键部分,它被称为路由入口,单页应用的部分更新均在router-view
所在的位置被显示,也就是说,路由路径对应的显示页由router-view路由路口所控制。
<template>
<!-- 动态组件 -->
<component :is="component" />
</template>
<script setup>
import {useRouter} from './index';
import { computed } from 'vue';
const router = useRouter();
// router-view 动态组件 展示依赖于 url的变化
// 响应式 router.current 设置为ref
const component = computed(()=>{
const route = router.routes.find( // 在路由配置数组内寻找是否有匹配的路径
(route)=>{
return route.path == router.current.value;
}
)
// 如果找到了,就将注册好了的路由路径返回去显示,否则就是null
return route? route.component : null
})
</script>
<style lang="scss" scoped>
</style>
这里使用动态组件展示不同路径对应的页面,下面是对计算属性computed内代码的解释,可以更好的理解代码。
.find()
方法是数组的迭代方法,它遍历数组中的每个元素,直到找到一个满足给定测试函数的第一个元素为止。在计算属性的函数内,测试函数是 (route) => route.path == router.current.value
,它检查每个 route
对象的 path
是否与当前路由的路径相等。如果找到匹配的元素,.find()
方法立即返回该元素;如果没有找到匹配的元素,则返回 undefined
。
下面就是对本次代码的演示了,总算是写完了,哈哈哈哈,感谢jym的观看哦!
转载自:https://juejin.cn/post/7393310486149545995