likes
comments
collection
share

手写vue的路由功能,深入理解路由源码

作者站长头像
站长
· 阅读数 18

路由简介

在 Vue.js 的开发中,经常被提及的“三大法宝”是指构建 Vue 应用程序时最常用到的三个核心部分。虽然这个说法非官方,但它广泛存在于社区中,用来描述开发 Vue 复杂应用时的核心工具集。以下是 Vue 的三大法宝:

  1. 组件系统 (Component System) : Vue 的组件系统是其核心特性之一,它允许开发者将 UI 分解为独立、可重用的组件。每个组件都有自己的模板、逻辑和样式,可以独立地开发和测试。组件的复用和组合能力极大地提高了开发效率和代码的可维护性。
  2. Vue Router: Vue Router 是 Vue.js 的官方路由管理器,用于处理单页面应用程序中的导航和视图切换。它提供了一种将 URL 映射到特定组件的方法,并且可以管理嵌套路由和复杂的导航逻辑。Vue Router 还支持过渡效果、懒加载组件和导航守卫等功能,使开发者能够创建流畅的用户体验。
  3. 状态管理库 (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的路由功能,深入理解路由源码

那么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的路由功能,深入理解路由源码

手写vue的路由功能,深入理解路由源码

手写vue的路由功能,深入理解路由源码

在 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的参数:

手写vue的路由功能,深入理解路由源码

<!-- 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

手写vue的路由功能,深入理解路由源码

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

它们主要用于做什么呢?

对路由进行初始化:

手写vue的路由功能,深入理解路由源码

在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函数,这个很像localStoragegetItemsetItem方法,一方存储,一方获取。

  • 示例

    import { createApp } from 'vue'
    
    const app = createApp(/* ... */)
    
    app.provide('message', 'hello')
    

    在应用的某个组件中:

    import { inject } from 'vue'
    
    export default {
      setup() {
        console.log(inject('message')) // 'hello'
      }
    }
    

手写vue的路由功能,深入理解路由源码

最后的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的观看哦!

手写vue的路由功能,深入理解路由源码

转载自:https://juejin.cn/post/7393310486149545995
评论
请登录