Vue Router 自己造轮—Hash Router 手写实现

638 阅读6分钟

在之前呢,我写了一篇有关于手写实现hash router 的文章手写Hash Router实战—我的学习心得分享 ,在上面这篇文章中,我只是使用html+js以及浏览器的api大概模拟了一下router-link的实现,那么这篇文章我将实现vue-hashrouter的基本功能,包括router-link 和 router-vue以及useRoute的基本实现

废话不多说,开始今天的主题

了解vue-router

首先我们先来解析一下官方的router,首先是先安装一个npm i vue-router依赖,然后在router文件夹下的index.js中使用createRouter创建路由,以及创建工厂函数createWebHashHistory。 配置好路由后,在入口文件main.js挂载--app.use(router)。我们先来搞清楚这几个函数的作用,才能更好的理解路由的实现。为此我翻阅了vue-router的官方文档

image.png

  1. createRouter 函数 在官方 Vue Router 中,createRouter 函数负责创建一个包含所有路由信息和配置的实例。为了简化,我们的 createRouter 将创建一个 Router 类的实例,并传入配置。

  2. createWebHashHistory 函数 这个函数用于创建一个 hash 模式的 history 对象,它会监听浏览器地址栏中的 hash 变化,并在变化时更新路由。

  3. app.use(router)

image.png 简单来说就是在 Vue 中,app.use 是用来安装 Vue 插件的。对于 Vue Router,这会注册全局的 router-link 和 router-view 组件,并设置一些全局的响应式数据,如当前路由。

Router实现

了解完后,我们来手写实现路由,首先我们先初始化路由和配置

  • 首先,我们需要创建一个 createRouter 函数,用于初始化路由实例。
import { ref } from 'vue';
import RouterLink from './RouterLink.vue';
import RouterView from './RouterView.vue';

export const createRouter = (options) => {
    return new Router(options);
};
  • 接下来,使用浏览器api hashchange实现 createWebHashHistory 函数,用于监听 hash 变化。
export const createWebHashHistory = () => {
    function bindEvents(fn) {
        window.addEventListener('hashchange', fn);
    }
    return {
        url: window.location.hash.slice(1) || '/',
        bindEvents
    };
};

bindEvents 是一个内部函数,其作用是绑定一个事件监听器到浏览器的 hashchange 事件上。这个事件会在浏览器地址栏中的 hash 部分发生变化时触发。fn 参数是将要执行的回调函数,每当 hashchange 事件发生时,这个函数会被调用。

    return {
        url: window.location.hash.slice(1) || '/',
        bindEvents
    };
};

函数 createWebHashHistory 返回一个对象,该对象包含两个属性:

  1. url:表示当前页面 hash 部分的值。如果 window.location.hash 的值以 # 开头,slice(1) 方法会移除这个 # 字符。如果没有 hash,则默认值为 '/'

  2. bindEvents:这是上面定义的 bindEvents 函数,它用于绑定 hashchange 事件。

createWebHashHistory 返回一个配置对象,这个对象可以被用作 Vue Router 的 history 配置选项。当 Vue Router 使用这个对象时,它会监听 hashchange 事件,并使用 url 属性来跟踪当前的 hash 路由。这样,Vue Router 可以在 hash 改变时更新应用的状态和视图。

  • 实现 Router 类 Router 类是整个路由系统的核心,负责管理路由状态和执行路由守卫。
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);
        app.component('router-link', RouterLink);
        app.component('router-view', RouterView);
    }
}
  • constructor(options): 构造函数接收一个配置对象,这个对象包含 history 和 routes 属性。history 是一个对象,包含当前 URL 的信息以及绑定事件的方法。routes 是一个数组,包含所有的路由配置信息。
  • this.current: 这是一个响应式的引用,它的值是当前路由的路径。当 URL 的 hash 部分发生变化时,这个值也会更新,从而触发 Vue 的响应式更新。
  • this.history.bindEvents(...): 这个方法用于监听浏览器地址栏中的 hash 变化。当 hash 变化时,回调函数会被调用,this.current 的值会被更新为新的 hash 值。
  • install(app): 这个方法用于将路由器实例安装到 Vue 应用中。它通过 app.provide 方法将路由器实例注入到应用上下文中,使得在任何组件中都可以通过 inject 方法来访问这个实例。同时,它还注册了全局的 router-link 和 router-view 组件。

最后我创建一个useRoute 钩子函数,

const ROUTER_KEY = '__router__'
// use 开头的是一派 hooks 函数式编程
export const useRoute = () => {
    return inject(ROUTER_KEY)
}

这就是路由的实现,接下来我们来实现一下router-link组件和router-view组件

实现 router-link 和 router-view

  1. RouterLink.vue

使用响应式路径 把使用组件时的to属性传进来使用,而 允许父组件传递任意内容到 RouterLink 组件内,通常是链接文本。

<template>
    <a :href="'#' + props.to">
        <slot></slot>
    </a>
</template>

<script setup>
import { defineProps } from 'vue';
const props = defineProps({
    to: {
        type: String,
        required: true
    }
});
</script>
  1. RouterView.vue

我在这里使用了<component> 标签,它是一个特殊的 Vue 组件,它可以动态地渲染任何组件。:is 绑定决定了要渲染哪个具体组件。我使用了自定义的useRoute把路径信息获取下来,然后使用计算属性查找与当前路由路径匹配的路由配置,并返回相应的组件。如果没有找到匹配项,则返回 null,<component>渲染为空。

<template>
    <component :is="component" />
</template>

<script setup>
import { computed } from 'vue';
import { useRoute } from './index';
import Home from '../../pages/Home.vue';

const router = useRoute();
const component = computed(() => {
    const route = router.routes.find(route => route.path === router.current.value);
    return route ? route.component : null;
});
</script>

使用

那么vue-router就已经封装好了,在router文件夹下的index.js文件中导入上述的createRouter方法和createWebHashHistory方法就可以开始使用了

我写好了index.js如下

import { createRouter,createWebHashHistory } from './grouter/index'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'

const router = createRouter({
    history: createWebHashHistory(),
    routes: [
        {
            path: '/',
            name: 'home',
            component: Home
        },
        {
            path: '/About',
            name: 'about',
            component: About
        }
    ]
})

export default router

然后我们在main.js中导入 且挂载

import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index.js'

const app = createApp(App)
// vue 方法做了什么事
// vue 只负责 组件思想,mvvm ?响应式 等核心,
// 其他的交给生态系统 一起开源 vue-router 是vue生态中的路由模块
// vue 和生态的对接呢? 就是这个use 方法
app.use(router)
app.mount('#app')

这是我的App.vue

<template>
  <nav>
    <router-link to="/">首页</router-link>
    <router-link to="/About">About</router-link>
  </nav>
  <main>
    <router-view></router-view>
  </main>
</template>

<script setup>

</script>

<style lang="scss" scoped>

</style>

创建好Home.vue和About.vue,就完成了

实现效果

2024-07-17211123-ezgif.com-video-to-gif-converter.gif

总结

在本文中,我深入探讨并亲手构建了一个简化的 Vue Hash Router,实现了路由的基本功能,包括 router-link 和 router-view 组件的搭建,以及 useRoute 的实用钩子函数。

从创建 createRouter 函数开始,我们逐步建立起路由实例,并通过 createWebHashHistory 监听浏览器的 hashchange 事件,确保了 URL 的变化能够被捕捉并及时更新应用状态。Router 类作为整个路由系统的中枢,管理着路由状态和事件,而 useRoute 提供了访问路由实例的便捷途径。

在 router-link 和 router-view 组件的设计中,我们运用了 Vue 的响应式系统和动态组件功能,实现了导航链接和视图切换的自动化。

感谢阅读,期待你在实际项目中应用和扩展这些知识!