手写简易版基于Hash的Vue Router:从原理的角度解读vue-router

我们平时写代码离不开使用路由来改变url而不刷新页面。你肯定知道怎么使用它,但是你知道它是如何实现的吗?本文将带你自己写一个简易版的Vue Router,让你初步了解它的原理是什么。

Vue Router

创建项目

我们首先创建一个叫simple-router的项目:

安装Vue Router

打开终端输入命令安装Vue Router(我这里的项目需要自己安装,我使用的是yarn):

各个文件的配置

main.js 引入并使用路由

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

router目录下的index.js 为页面配置路由

javascript 复制代码
import {createRouter,createWebHashHistory} from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const routes = [
    {
        path: '/',
        name: 'home',
        component: Home 
    },
    {
        path: '/about',
        name: 'about',
        component: About
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes
})

export default router

App.vue

xml 复制代码
<template>
  <div nav>
    <router-link to="/">Home</router-link>
     | 
    <router-link to="/about">About</router-link>
  </div>
  <router-view />
</template>

Home.vue

xml 复制代码
<template>
    <div>
        home page
    </div>
</template>

About.vue

xml 复制代码
<template>
    <div>
        about page
    </div>
</template>

页面效果如下,点击能去到相应页面:

现在我们不使用安装的Vue Router,手写来实现它的功能。

基于Hash实现Vue Router

步骤

首先我们要知道刚刚我们使用路由有哪些步骤:

  1. 创建一个路由实例,并定义路由规则('router/index.js'中)
  2. 在Vue 主应用实例中,引入并使用创建的路由实例('main.js'中)
  3. 在模板中使用 <router-view> 组件来渲染匹配当前 URL 的组件('App.vue'中)
  4. 在模板中使用 <router-link> 组件来创建可以点击的链接,使用户能够导航到不同的路由('App.vue'中)

这些步骤中,创建路由实例用到的createRoutercreateWebHashHistory 方法,使用路由实例用到的useRouter方法,还有<router-view><router-link> 组件,都是Vue Router中的。那么我们要实现的就是它们。

我们在router目录下新建一个myRouter文件夹,在里面新建index.js,RouterLink.vue,RouterView.vue文件,我们的功能将在这里面实现。

将router/index.js中引入的'vue-router'修改为'./myRouter':

javascript 复制代码
import { createRouter,createWebHashHistory } from './myRouter'

路由实例

我们来看myRouter/index.js:

javascript 复制代码
import { inject,ref } from 'vue'

const ROUTER_KEY = '__router__'

export function useRouter(){
    return inject(ROUTER_KEY)
}
export function createRouter(options){
    return new Router(options)
}

export function createWebHashHistory(){
    function bindEvents(fn){
        window.addEventListener('hashchange', fn)
    }
    return {
        bindEvents,
        url: window.location.hash.slice(1) || '/'
    }
}

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_KEY, this)
    }
}

解释一下这段代码:

我们要用createRouter函数来创建路由实例,那我们就需要有一个Router类并通过它创建实例,createRouter函数接受一个options对象作为参数并将这个参数传递给Router类,当我们调用createRouter函数时,就会返回一个通过 new Router类创建出来的实例。

这个options参数就是我们创建一个路由实例时传进去的对象,比如说router/index.js中我们创建路由实例时是这样的:

createWebHashHistory函数用于创建一个基于hash模式的路由历史。里面有一个bindEvents方法,接受一个回调函数fn,它用于绑定 hashchange 事件监听器,当URL发生了改变时就会触发回调函数fn。它返回一个对象,其中包含bindEvents 方法和 url 属性。url 属性表示当前的 URL,它从 window.location.hash 中获取,slice(1)去除了开头的 # 符号。

Router类是一个路由器类,负责管理路由的核心逻辑。它在构造函数constructor中接受一个 options 对象,其中包含 historyroutes 属性。history 对象用于管理路由历史,routes 是一个路由规则数组。这里实际上history的值就是我们上面写的createWebHashHistory()调用结果,routes的值就是我们之前配置各个页面路径的数组。current 是一个响应式的引用,表示当前的路径,当路径发生改变时,它的值也会随之更新。在构造函数中,它将 history.url 的值赋给 current.value,并通过调用 history.bindEvents 来绑定 hashchange 事件监听器,以便在 URL 改变时更新 current.value

其中install 方法是必须要有的,app.provide(ROUTER_KEY, this) 语句是在 Vue 应用程序中将路由实例注册为一个全局的依赖,以便在全局使用。它使用了 Vue 3 中新增的 provide/inject API。具体来说,provide 方法用于在 Vue 应用程序中注册一个全局依赖,可以在所有后代组件中访问。它接受两个参数:依赖名称和依赖值。在这段代码中,依赖名称为 ROUTER_KEY,依赖值为当前的路由实例 this。这意味着,在任何需要访问路由实例的组件中,都可以使用 inject 方法来注入该实例。

useRouter 函数是一个自定义的实用函数,用于在组件中获取路由实例。它使用了 inject 函数来注入在父组件provide方法中注册的路由实例。当我们在页面中需要使用路由实例时,只需要调用useRouter方法就可以拿到路由实例了。

<router-link>

这个功能的实现在RouterLink.vue中:

xml 复制代码
<template>
    <a :href="'#' + to">
        <slot />  // 插槽,让我们使用RouterLink标签时中间能够放东西
    </a>
</template>

<script setup>
defineProps({
    to: {
        type: String,
        required: true // 表示to为必传项
    }
})
</script>

我们还需要将其注册为全局组件以便它能在各处使用,那么就需要在myRouter/index.js中引入它并为它注册,RouterView也同样:

引入:

在Router类的install方法中注册为全局:

<router-view>

xml 复制代码
<template>
    <component :is="component"></component>
</template>

<script setup>
import { computed } from 'vue'
import { useRouter } from '../myRouter'

const router = useRouter() // 在当前组件注入了router路由实例

const component = computed(() => {
    // 如果当前的url是'/',就返回Home.vue
    const route = router.routes.find((route) => {
        return route.path === router.current.value
    })
    return route ? route.component : null
})
</script>

解释一下这段代码:

代码中的模板部分使用了动态组件 <component>,并绑定了一个 is 属性,该属性的值为一个计算属性 component,如下:

component使用了 computed 函数来定义,并返回一个根据当前 URL 找到的对应组件的名称。具体来说,它首先调用 useRouter 函数来获取当前的路由实例 router,然后遍历该路由实例的 routes 属性,找到其中 path 属性与当前 URL 中的路径相同的那个路由规则。如果找到了对应的路由规则,则返回该规则的 component 属性,即对应的组件名称;否则返回 null

最终,component 的值作为 <component> 组件的 is 属性的值,从而动态渲染对应的组件。

现在我们可以使用这两个标签了,在App.vue中修改:

xml 复制代码
<template>
  <div nav>
    <RouterLink to="/">Home</RouterLink>
     | 
    <RouterLink to="/about">About</RouterLink>
  </div>
  <RouterView />
</template>

现在来到页面上,可以正常跳转:

总的来说,我们的代码简单地实现了一个基本的 Vue Router 功能,包括获取路由实例、创建路由实例和创建基于 hash 的路由历史。这是一个较为简化的示例,实际的 Vue Router 功能更为复杂和全面,涉及到更多的特性和配置。如果你想要更深入地了解 Vue Router,可以去查阅官方文档哦。

本文到这就结束啦,欢迎下次再来一起学习ヾ(◍°∇°◍)ノ゙!!

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax