手写 vue-router

前言

在构建 Vue 项目的过程中,vue-router 是不可或缺的核心工具之一,它为我们带来了便捷的路由管理和页面导航功能。通过<router-link /><router-view />,我们可以轻松地在组件之间跳转,构建单页面应用的基础架构。虽然 Vue 提供了强大的工具和插件生态,但如果你想深入了解它们的工作原理,动手实现一个简单的vue-router,无疑是一个非常好的学习途径。在本文中,我们将从头开始,手写一个简化版的 vue-router,带你了解其核心机制,并通过这一过程加深对 Vue 框架的理解。无论你是初学者还是有经验的开发者,希望这篇文章能为你提供有价值的见解。

全局组件

当我们的项目引入router时,我们不需要引入<router-link /><router-view />就可以在任何地方直接访问,因为它们是全局组件。可以使用app.component()方法让我们自己写的组件变为全局组件去使用。手写vue-router,需要我们自己去自定义组件,当然如果我们没有引入组件,dom会把它当作一般标签来解析。

use 方法做了什么事?

在 Vue 的架构体系中,Vue 本身主要专注于构建组件的核心思想,致力于打造诸如 MVVM 模式和响应式机制等关键特性。然而,为了提供更全面、更丰富的功能,Vue 将许多扩展功能的实现交给了其繁荣的生态系统,并以开源的方式共同发展。

其中,vue-router作为 Vue 生态中的重要路由模块,承担着页面导航和路由管理的关键职责。而 Vue 与它所属生态系统中的各个模块进行对接和整合的关键桥梁,正是这个use方法。

通过use方法,Vue 能够灵活地引入和集成来自生态系统的各种功能模块,实现功能的扩展和增强,为开发者提供了更加便捷、高效的开发体验,共同构建出强大且多样化的应用。

src目录结构

基础配置

使用原来写法写路由配置,我们自己去手写createRoutercreateWebHashHistory

javaScript 复制代码
import { createRouter, createWebHashHistory } from './grouter/index';
import Home from '../pages/Home.vue';
import About from '../pages/About.vue';

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

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

export default router;

About.vue和Home.vue就简单的写一些基础代码,能显示基本的信息即可。

Vue 复制代码
<template>
  <div>About</div>
</template>

<template>
  <div>Home</div>
</template>

App.vue写上<router-view /><router-link />组件,同时要手写这两个组件,实现vue-router的效果。

Vue 复制代码
<template>
  <header>
    <nav>
      <router-link to="/">首页</router-link>
      <router-link to="/about">关于</router-link>
    </nav>
  </header>
  <main>
    <router-view></router-view>
  </main>
  <footer></footer>
</template>

手写源码

最重要的手写源码放在grouter文件夹下,我们引入的router就在这自己手写。

1. index.js

javaScript 复制代码
import RouterLink from './RouterLink.vue';
import RouterView from './RouterView.vue';
import { ref, inject } from 'vue';

// 单例的责任
export const createRouter = (options) => {
  return new Router(options);
};

export const createWebHashHistory = () => {
  function bindEvents(fn) {
    window.addEventListener('hashchange', fn);
  }

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

// 标记一下 router 要向全世界暴露
// 常量配置项 全部大写
const ROUTER_KEY = '__router__';

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

class Router {
  constructor(options) {
    this.history = options.history;
    this.routes = options.routes;
    this.current = ref(this.history.url);
    this.history.bindEvents(() => {
      // console.log('////////');
      this.current.value = window.location.hash.slice(1);
    });
  }

  // use 调用 插件install方法
  install(app) {
    // 全局声明有一个router 全局使用的对象
    app.provide(ROUTER_KEY, this);
    console.log('准备与vue 对接', app);
    app.component('router-link', RouterLink);
    app.component('router-view', RouterView);
  }
}

createRouter 是一个工厂函数,用于创建一个 Router 实例。它接受一个 options 参数,其中包含了路由配置。

createWebHashHistory 是一个用于创建基于 hash 的历史记录管理器的函数。它返回一个对象,该对象包含了当前 URL(去掉了 # 的部分)和一个 bindEvents 方法,用于监听 hashchange 事件。这种模式使用 URL 的 # 部分作为路径,从而实现无刷新路由。

useRouter 是一个函数式编程的 Hook,用于在 Vue 组件中访问路由器实例。它使用 Vue 的 inject 函数,通过 ROUTER_KEY 从依赖注入系统中获取路由器实例。

路由Router中的constuctor:

  • this.history:存储路由历史管理对象,控制路由状态。
  • this.routes:存储路由配置。
  • this.current:使用 Vue 的 ref 创建一个响应式变量,用于存储当前的路径。
  • this.history.bindEvents() :绑定一个事件监听器,当 URL 中的 hash 值发生变化时,更新 this.current 的值。

install 方法是 Vue 插件模式的一部分。当通过 app.use(router) 安装路由器插件时,Vue 会调用这个方法。

  • app.provide(ROUTER_KEY, this) :通过 provide 方法向 Vue 的依赖注入系统中注册路由器实例,以便在应用中的任何组件中都能通过 inject 访问。
  • app.component('router-link', RouterLink)app.component('router-view', RouterView) :注册了两个全局组件 RouterLinkRouterView,分别用于导航和显示当前路由对应的组件。

插槽

如何把组件中间的文字传给RouterLink组件呢?我们使用插槽,组件到底显示什么东西,交给外部去自定义,提升了组件的可复用性,不用props,提升组件的定制性。

2. RouterLink:

Vue 复制代码
<template>
  <a :href="'#' + props.to">
    <slot></slot>
  </a>
</template>

<script setup>
import { defineProps } from 'vue';
const props = defineProps({
  to: {
    type: String,
    required: true,
  },
});
</script>
  • 定义了一个 <a> 链接标签,来实现router-link的跳转功能,:href 属性加上:变成动态值。props.to 是组件的一个属性,表示目标路由路径。'#' + props.to 表示这个链接的 href 值是一个 hash 值。
  • <slot> 是一个插槽,用于在使用这个组件时插入自定义的内容。插槽的内容将显示在 <a> 标签内。

3. RouterView:

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

<script setup>
import { useRouter } from './index.js';
import { computed } from 'vue';

const router = useRouter();
console.log(router);

// ref 处理私有数据状态
// router-view 动态组件展示 依赖于url的变化  需要重新计算
// 响应式 router.current  设置为ref
const component = computed(() => {
  const route = router.routes.find(
    (route) => route.path == router.current.value
  );
  console.log('/////');
  return route ? route.component : null;
});
</script>

<component> 是 Vue 提供的动态组件,可以根据 :is 绑定的值来渲染不同的组件。computed计算属性,因为router-view需要动态展示内容,依赖于url的变化,所以需要重新计算,我们就用computed属性。

结语

通过这次手写 vue-router 的练习,我们不仅对 Vue 的路由机制有了更深入的理解,也体验了 Vue 插件的强大扩展性。从定义全局组件到实现动态组件渲染,再到利用插槽提高组件的复用性,每一个步骤都让我们更好地掌握了 Vue 的核心思想。尽管我们实现的是一个简化版的 vue-router,但它包含的原理和逻辑却是我们在日常开发中不可忽视的基础。希望通过这篇文章,能激发你对 Vue 更大的兴趣,并帮助你在前端开发的道路上走得更远。无论是日后的项目开发,还是面对复杂的业务需求,掌握这些底层知识,都将是你不可或缺的技能储备。

相关推荐
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.7 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
2401_8576009510 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_8576009510 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL10 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
轻口味10 小时前
Vue.js 核心概念:模板、指令、数据绑定
vue.js
2402_8575834910 小时前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
java_heartLake11 小时前
Vue3之性能优化
javascript·vue.js·性能优化
ddd君3177412 小时前
组件的声明、创建、渲染
vue.js
前端没钱13 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js