手写简易版基于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,可以去查阅官方文档哦。

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

相关推荐
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
LCG元6 小时前
【面试问题】JIT 是什么?和 JVM 什么关系?
面试·职场和发展
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235249 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_7482402510 小时前
前端如何检测用户登录状态是否过期
前端