likes
comments
collection
share

手撕vue-router - 源码分析

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

前言

vue-router是Vue.js官方提供的路由管理器,专门设计用于构建单页面应用(Single Page Application, SPA)。在单页面应用中,多个“页面”状态可以在同一个HTML文档中呈现,无需重新加载整个页面。

那么vue-router是如何实现的呢,通过它的源码我们来手写一下vue-router包(实现router-link、router-view 组件

vue-router

使用步骤

1. 安装 vue-router

如果你使用的是Vue创建的项目,可以通过以下命令安装vue-router

npm install vue-router
# 或者使用yarn
yarn add vue-router

2. 配置路由

在你的项目中创建一个router目录,并在其中创建一个index.jsindex.ts文件(如果你的项目支持TypeScript)作为入口文件。

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;

在这个文件中,你需要导入createRoutercreateWebHistory/createWebHashHistory函数,并定义你的路由。

routes数组包含了所有的路由配置。每个路由配置对象至少包含一个path和一个component属性。

createWebHistorycreateWebHashHistory的区别

在Vue Router中,createWebHistorycreateWebHashHistory是用于创建不同路由模式的函数。这两种模式的主要区别在于它们如何处理和呈现URL,以及它们对服务器配置的要求。

  1. createWebHashHistory

    • 这个模式使用URL的哈希部分(即#后面的部分)来保存路由信息。例如,如果你的页面是在https://example.com/,并且你导航到一个内部路由如/about,URL会变成https://example.com/#/about

    • 由于URL的哈希部分不会被发送到服务器,这意味着在服务器上不需要任何特殊的配置来处理这些路由。即使服务器不知道这些路由的存在,页面也能正确加载。

    • 哈希模式通常在服务器端没有正确配置的情况下使用,或者在无法控制服务器配置的环境中使用,比如GitHub Pages。

  2. createWebHistory

    • 这个模式使用HTML5的History API(包括pushStatereplaceState)来改变浏览器的URL,并且URL中不会包含哈希符号。例如,对于相同的/about路由,URL会显示为https://example.com/about

    • 使用历史模式时,服务器需要配置来处理这些URL,因为它们看起来像普通的文件或目录路径。如果没有正确的配置,尝试访问这些URL可能会导致404错误,因为服务器可能试图查找并不存在的文件或目录。

    • 历史模式提供了更干净的URL,但是需要服务器端的配合才能工作得当。

3. 在主应用中使用路由

在你的main.jsmain.ts文件中,导入刚刚创建的路由器,并将其添加到Vue应用的实例中。

import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import router from "./router";

const app = createApp(App);
app.use(router);
app.mount("#app");

手撕vue-router —— grouter

上述我们已知通过下载一个vue-router包,现在我们来仿写一个这样的包(grouter)实现路由功能。

手撕vue-router - 源码分析

router/index.js入口文件中改变导入包资源,不再使用vue-router,采用仿写的grouter.

手撕vue-router - 源码分析

index.js(grouter入口文件)

// 导入Vue组件和Vue提供的响应式工具
import RouterLink from "./RouterLink.vue";
import RouterView from "./RouterView.vue";
import { ref, inject } from "vue";

// 定义一个常量用于提供和注入router对象的key
const ROUTER_KEY = "router";

// 在任何地方,使用 useRouter 就可以拿到router 对象
const useRouter = () => {
  return inject(ROUTER_KEY); // 使用Vue的inject函数获取到注入的router对象
};

// 创建路由器的工厂函数,接收配置选项并返回一个新的Router实例
const createRouter = (options) => {
  return new Router(options); // 返回一个新的Router实例
};

// 创建基于Web Hash的历史记录对象的工厂函数
const createWebHashHistory = () => {
  // 内部函数,用于绑定hashchange事件
  function bindEvents(fn) {
    window.addEventListener("hashchange", fn); // 监听浏览器地址栏hash变化事件
  }
  
  // 返回一个对象,包含当前URL和绑定事件的方法
  return {
    url: window.location.hash.slice(1) || "/", // 获取当前的hash值,作为初始url,如果不存在则默认为"/"
    bindEvents, // 返回绑定事件的方法
  };
};

// 定义一个Router类,用于管理路由
class Router {
  constructor(options) {
    console.log(options); // 输出传入的配置选项
  
    this.history = options.history; // 设置路由历史管理对象
    this.routes = options.routes; // 设置路由配置数组
  
    // 当前URL的状态,使用Vue的ref函数创建响应式引用
    this.current = ref(this.history.url);
  
    // 绑定hashchange事件,监听URL变化并更新当前URL的状态
    this.history.bindEvents(() => {
      this.current.value = window.location.hash.slice(1); // 更新当前URL的值
    });
  }

  // 安装方法,用于将路由器集成到Vue应用中
  install(app) {
    console.log("vue 对接"); // 输出日志,表示开始集成到Vue应用中
  
    // 提供router对象,使其可以在应用的任何地方通过useRouter获取到
    app.provide(ROUTER_KEY, this);
  
    // 声明全局组件,使得<router-link>和<router-view>可以在应用中使用
    app.component("router-link", RouterLink);
    app.component("router-view", RouterView);
  }
}

// 导出模块中定义的函数和类,供其他地方使用
export { createRouter, createWebHashHistory, useRouter };
  • 响应式 URL 状态管理:

    • 使用 Vue 的 ref 函数创建响应式的 current 属性,用于跟踪当前的 URL。
    • 当 URL 发生变化时,通过监听 hashchange 事件更新 current 的值。
  • 依赖注入:

    • 使用 provideinject 实现依赖注入,使得 router 对象可以在 Vue 应用的任何地方使用。
  • 全局组件注册:

    • Router 类的 install 方法中,使用 app.component 注册全局组件 <router-link><router-view>,使得这些组件可以在应用中随处使用。
  • 路由器工厂函数:

    • 提供 createRoutercreateWebHashHistory 工厂函数,方便创建和配置路由器实例及历史记录对象。
  • 事件绑定:

    • 通过 bindEvents 函数绑定 hashchange 事件,实时监听 URL 的变化并做出相应更新。

Router对象:

手撕vue-router - 源码分析

router-link

<template>
  <!-- 动态生成的链接,根据传入的 'to' 属性设置 href -->
  <a :href="'#' + props.to">
    <!-- 插槽,用于插入内容 -->
    <slot></slot>
  </a>
</template>

<script setup>
import { defineProps } from "vue";

// 定义组件的 props
const props = defineProps({
  to: {
    type: String, 
    required: true, // 'to' 属性是必须传入的
  },
});
</script>

<style lang="css" scoped></style>

自定义的 router-link 组件实现:

  • 动态生成链接

    • 使用绑定语法 :href="'#' + props.to",可以根据传入的 to 属性动态生成链接。这使得组件可以灵活地用于不同的链接目标,不需要在使用时写死链接路径。
  • 插槽的使用

    • 插槽 <slot></slot> 允许组件用户在使用组件时插入任意内容。这样,用户可以在组件内自定义链接文本或嵌入其他 HTML 元素,增强组件的灵活性和可重用性。
  • 定义属性

    • 使用 defineProps 函数定义组件的 props,确保组件可以接收和验证外部传入的属性。通过设置 required: true,确保组件在使用时必须传入 to 属性,从而避免因为缺少必要属性导致的错误。

router-view

<template>
  <!-- 动态组件渲染,根据计算属性 'component' 动态渲染对应的组件 -->
  <component :is="component"></component>
</template>

<script setup>
import { computed } from "vue"; // 导入 Vue 的 computed 函数
import { useRouter } from "./index"; // 导入自定义的 useRouter 函数

// 获取全局路由对象
let router = useRouter();

// 动态响应式的计算属性,根据当前 URL 动态计算出要渲染的组件
const component = computed(() => {

  // 在路由配置中查找当前路径匹配的路由
  const route = router.routes.find(
    (route) => route.path === router.current.value
  );
  // 返回匹配的组件,如果没有匹配则返回 null
  return route ? route.component : null;
});
</script>

<style lang="css" scoped></style> 

自定义 router-view 组件的实现结合了 Vue 3 的 Composition API 和动态组件渲染,使得视图能够根据当前 URL 动态渲染对应的组件。

  • 动态组件渲染: 根据当前 URL 动态渲染对应的组件,使用 <component> 标签和计算属性 component 实现。

  • 响应式路由管理: 使用 Vue 的 Composition API 中的 computed 函数创建响应式计算属性,根据路由对象的当前状态动态更新视图。

  • 依赖注入: 使用 useRouter 函数通过依赖注入获取全局路由对象,使得路由对象在任何组件中都可以方便地访问和使用。

效果

手撕vue-router - 源码分析

总结

最后,我们就手写实现了grouter包(router-link、router-view 组件),实现简单的路由功能。但是实际的实现更为复杂精细,有兴趣的小伙伴可以去阅读全部的源码。深入阅读和理解一个完整的路由器的源码可以帮助开发者扩展视野,学习设计模式和最佳实践,并且理解复杂系统的组织和实现方式。

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