💡Vue 简介

来自官网 https://cn.vuejs.org

1. 概况

两个核心功能:

  1. 声明式渲染:Vue 基于标准 HTML 拓展了一套语法

  2. 响应式:JavaScript 状态改变时会(自动)响应式地更新 DOM

编程模型:声明式、组件化(单文件组件

1.1 使用 Vue 的方式

  1. 构建 Web 应用

    1. 引入 JS 文件:无需构建步骤,渐进式增强静态的 HTML

    2. 作为 Web Components 嵌入

    3. 单页面应用(Single-Page Application,SPA)

      • 特征:前端有丰富的交互性、较深的会话和复杂的状态逻辑

      • 问题:纯客户端的 SPA 在首屏加载和 SEO 方面,因为浏览器会收到一个巨大的 HTML 空页面,只有等到 JavaScript 加载完毕才会渲染出内容

    4. 服务端渲染(Server-Side Rendering,SSR)

      • 好处:能极大地改善应用在 Web 核心指标上的性能表现

    5. 静态站点生成:所需数据是静态的,所以服务端渲染可以提前完成,即将站点预渲染为静态 HTML

  2. Web 之外

    1. 桌面应用

    2. 移动端应用

    3. 渲染器,比如 WebGL、终端命令行

更多内容可查阅使用 Vue 的多种方式,Vue 的核心知识在这些不同的使用方式中都是通用的。

1.2 组件的书写风格

  1. Options API,选项式 API

    • 用一个对象来描述组件的逻辑

    • 适用场景:不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue

  2. Composition API,组合式 API

    • 可以使用导入的 API 函数来描述组件逻辑

    • 适用场景:打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件

实际上,选项式 API 是在组合式 API 的基础上实现的。大部分的核心概念在这两种风格之间都是通用的,熟悉了一种风格以后也能很快地理解另一种风格。

选项式 API 以“组件实例”的概念为中心 (即上述例子中的 this),对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码,从而对初学者而言更为友好。

组合式 API 的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。

1.3 组合式 API

组合式 API 是 Vue 3 和 Vue 2.7 的内置功能。在 Vue 3 中,组合式 API 基本上都会配合 <script setup> 语法在单文件组件中使用。

组合式 API 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语,涵盖了以下方面的 API:

说明:虽然这套 API 的风格是基于函数的组合,但组合式 API 并不是函数式编程。组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变。

组合式 API 的好处:

  1. 更好的逻辑复用:

    • 使我们能用组合式函数。选项式 API 主要的逻辑复用机制是 mixins,而组合式 API 解决了 mixins 的所有缺陷

    • 组合式 API 也更利于孵化其它社区项目,比如 VueUse(工具型组合式函数集合), RxJS(第三方状态管理库和 Vue 的响应式系统之间的集成)

  2. 更灵活的代码组织

    • 当需要处理多个逻辑关注点的组件时,选项式 API 会有不便之处(处理相同逻辑关注点的代码被强制拆分在了不同的选项中)

    • 也方便重构,利于后期维护

  3. 更好的类型推导:组合式 API 主要利用基本的变量和函数,它们本身就是类型友好的

    • 选项式 API(2013 年)的类型推导很复杂,且在处理 mixins 和依赖注入类型时依然不太理想

    • 想用 TS 的 Vue 开发者用了由 vue-class-component 提供的 Class API(非常依赖 ES 装饰器,2019 年时还是个不稳定的语言特性)

    • 基于 Class 的 API 和选项式 API 在逻辑复用和代码组织方面存在相同的限制

  4. 更小的生产包体积

    • <script setup> 书写的组件模板被编译为了一个内联函数,和 <script setup> 的代码位于同一个作用域,所以本地变量的名字可以被压缩

    • 选项式 API 需要依赖 this 上下文对象来访问属性,然而对象的属性名不能被压缩

组合式 API 能够覆盖所有状态逻辑方面的需求,如果使用 <script setup>,那么 inheritAttrs 应该是唯一一个需要用额外的 <script> 块书写的选项了。Vue 的组合式 API 仅调用 setup()<script setup> 的代码一次。

1.4 单文件组件

单文件组件,Single-File Component,简称 SFC,将一个 Vue 组件的模板、逻辑与样式封装在单个文件中,完整的语法定义详见 SFC 语法定义

Vue SFC 是一个框架指定的文件格式,会由 @vue/compiler-sfc 编译为标准的 JavaScript 和 CSS,一个编译后的 SFC 是一个标准的 JavaScript(ES) 模块。可在 Vue SFC Playground 中看到被最终编译后的样子。

在实际项目中,我们一般会使用集成了 SFC 编译器的构建工具,比如 Vite 或者 Vue CLI (基于 webpack),Vue 官方也提供了脚手架工具来帮助我们尽可能快速地上手开发 SFC。

1.5 本地构建

  • Vue CLI, Command Line Interface

  • Vite

Vue 官方的构建流程是基于 Vite 的。需要安装 15.0 或更高版本的 Node.js。关于如何为一个 Vite 项目配置 Vue 相关的特殊行为,可查看 @vitejs/plugin-vue

使用 Vite 来创建一个 Vue 项目,运行 npm init vue@latest 命令。它会安装和执行 create-vue,一个 Vue 提供的官方脚手架工具:

✔ Project name: … vue-project
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
  1. 推荐 IDE 配置:VS Code + Volar 扩展

    • Volar 提供了语法高亮、TypeScript 支持,以及模板内表达式与组件 props 的智能提示

    • Volar 取代了之前为 Vue 2 提供的官方 VSCode 扩展 Vetur

  2. 浏览器开发插件 Vue.js devtools。可以浏览一个 Vue 应用的组件树,查看各个组件的状态,追踪状态管理的事件,还可以进行组件性能分析。

  3. eslint-plugin-vue,一个 ESLint 插件,会提供 SFC 相关规则的定义。由 Vue 团队维护

  4. 底层库

    • @vue/compiler-sfc 提供了处理 Vue SFC 的底层的功能

    • @vitejs/plugin-vue 为 Vite 提供 Vue SFC 支持的官方插件

    • vue-loader 为 webpack 提供 Vue SFC 支持的官方 loader

更多工具,详见官网文档 工具链

2. 创建应用

2.1 创建一个新的应用实例

每个 Vue 应用都是通过 createApp() 函数创建一个新的应用实例,比如:

import { createApp } from "vue";

const app = createApp({
  /* 根组件选项 */
});

传入 createApp() 的对象实际上是一个组件,我们也可以这样写:

import { createApp } from "vue";

import App from "./App.vue"; // 从一个单文件组件中导入根组件

const app = createApp(App);

每个应用都需要一个根组件,其它组件将作为其子组件。大多数真实的应用都是由一棵嵌套的、可重用的组件树组成的。

关于应用实例的几点说明:

  1. 应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项

    • 比如配置应用级的错误处理器

    • 比如显式地添加全局对象,供所有的 Vue 表达式使用

  2. 应用实例提供了一些方法来注册应用范围内可用的资源

  3. 应用实例并不只限于一个。createApp() 允许我们在同一个页面中创建多个共存的 Vue 应用,而且每个应用都拥有自己的用于配置和全局资源的作用域,比如在服务端渲染时通常会创建多个小的应用实例,然后将它们分别挂载到所需的元素上去。

// 1. 配置一些应用级的选项
app.config.errorHandler = err => {
  /* 处理错误 */
};
app.config.globalProperties = ...;
// 2. 注册应用范围内可用的资源,eg.注册一个组件
app.component("TodoDeleteButton", TodoDeleteButton);
// 3. 用 createApp() 在同一个页面中创建多个共存的 Vue 应用
const app1 = createApp({});
app1.mount("#container-1");

const app2 = createApp({});
app2.mount("#container-2");

2.2 挂载应用

应用实例只有在调用了 .mount() 方法后才会被渲染出来,即挂载应用mount() 方法接收一个“容器”参数,可以是一个实际的 DOM 元素,也可以是一个 CSS 选择器字符串。

app.mount("#app"); // 返回根组件实例,而不是应用实例

需注意:

  1. .mount() 方法的返回值是根组件实例而不是应用实例(这不同于其它资源注册方法)

  2. .mount() 方法应该始终在整个应用配置和资源注册完成后被调用

  3. 确保在挂载应用实例之前,完成所有应用配置!

3. 模板语法

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。 在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。

当然,也可以不使用模板,而直接使用 JavaScript——采用渲染函数 & JSX 的方式。需要注意的是,此时将不会享受到和模板同等级别的编译时优化。

3.1 数据绑定

  1. 文本插值 {{ msg }}:最基本的数据绑定形式

  2. 指令 v-bind 响应式地绑定一个 attribute

  3. 指令 v-html 插入纯 HTML,比如 <span v-html="rawHtml"></span>

    • 插值为纯 HTML,将会忽略数据绑定

    • 不建议在网站上动态渲染任意 HTML,因为非常容易造成 XSS 漏洞

    • 在使用 Vue 时,应当使用组件作为 UI 重用和组合的基本单元

<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div> <!-- 可简写,因为 v-bind 非常常用 -->

<!-- 一次绑定多个 attributes -->
<div v-bind="obj"></div>

<!-- 普通属性 vs 布尔属性 -->

在所有的数据绑定中都支持完整的 JavaScript 表达式,作用域是组件。相关说明:

  1. 每个绑定仅支持单一表达式,可以简单地理解为可以合法地写在 return 后面的

  2. 绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用(修改/删除)

  3. 模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表app.config 可添加应用级)

3.2 指令

Vue 指令,以 v- 开头的特殊 attribute。

指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,v-for, v-on, v-slot)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。

完整的指令语法:

  1. 指令名

  2. 指令参数,其中动态参数的值和语法都有相应的限制

  3. 指令修饰符

  4. 指令值

<p v-if="seen">Now you see me</p>

<!-- 2. 带参数 -->
<a v-bind:href="url"> ... </a>
<a :href="url"> ... </a> <!-- 简写 : -->

<a v-on:click="doSomething"> ... </a>
<a @click="doSomething"> ... </a> <!-- 简写 @ -->

<!-- 2. 动态参数,即参数用了个 JS 表达式 -->
<a v-bind:[attributeName]="url"> ... </a>
<a :[attributeName]="url"> ... </a> <!-- 简写 : -->

<a v-on:[eventName]="doSomething"> ... </a>
<a @[eventName]="doSomething"> <!-- 简写 @ -->

<!-- 3. 修饰符 -->
<form @submit.prevent="onSubmit">...</form>

常用指令的用法,见另一篇笔记《Vue 指令》

3.3 模板引用

虽然 Vue 的声明性渲染模型已经为我们抽象了大部分对 DOM 的直接操作,但有时候依然需要直接访问底层 DOM 元素,比如将焦点设置在某个 input 元素上,比如要在一个元素上初始化一个第三方库。

这时就用到了一个特殊的 attribute ref(模板引用),它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。

<input ref="input" />

需要声明一个 ref 来存放该元素的引用,必须和模板里的 ref 同名。比如:

<script setup>
  import { ref, onMounted } from "vue";

  const input = ref(null); // input

  // 注意:
  onMounted(() => {
    input.value.focus();
  });
</script>

<template>
  <input ref="input" />
  <!-- input -->
</template>
export default {
  setup() {
    const input = ref(null);
    // ...
    return {
      input
    };
  }
};
  1. 使用时机

    • 我们只可以在组件挂载后才能访问模板引用

    • 如果是想在模板中的表达式上访问模板引用,那需要写个侦听器 watchEffect,因为初次渲染时模板引用的值会是 null

  2. 更多场景

    • v-for 中的模板引用

    • 函数模板引用

    • 组件上的 ref

4. 响应式

4.1 创建一个响应式对象或数组

使用 reactive() 函数创建一个响应式对象或数组。

import { reactive } from "vue";

const state = reactive({ count: 0 });

reactive() 返回的是响应式对象其实是 JavaScript Proxy,详见 深入响应式系统

  1. 响应式代理 vs 原始对象

    1. Proxy 对象和原始对象是不相等的

    2. 只有代理对象是响应式的,更改原始对象不会触发更新

  2. reactive() API 的两条限制

    1. 仅对对象类型有效,对原始类型无效

    2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,所以我们必须始终保持对该响应式对象的相同引用

      • 不可以随意地“替换”一个响应式对象,这将导致对初始引用的响应性连接丢失

      • 当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性

  3. 深层响应式对象 vs 浅层响应式对象

  4. DOM 的更新时机

    • DOM 的更新并不是同步的,Vue 会缓冲它们直到更新周期的“下个时机”,以确保无论我们进行了多少次声明更改,每个组件都只需要更新一次

    • 全局 API nextTick() 可确保等待一个状态改变后的 DOM 更新完成

4.2 使用响应式状态

要在组件模板中使用响应式状态,需要在 setup() 函数中定义并返回。比如:

import { reactive } from "vue";

export default {
  // `setup` 是一个专门用于组合式 API 的特殊钩子函数
  setup() {
    const state = reactive({ count: 0 });

    // 暴露 state 到模板
    return {
      state
    };
  }
};
<div>{{ state.count }}</div>

考虑到在 setup() 函数中手动暴露大量的状态和方法会非常繁琐,通常我们会使用 <script setup> 来大幅度地简化代码。如下:

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

  const state = reactive({ count: 0 });

  function increment() {
    state.count++;
  }
</script>

<template>
  <button @click="increment">
    {{ state.count }}
  </button>
</template>

4.3 响应式引用

reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的“引用”机制。为此,Vue 提供了一个 ref() 方法允许我们创建可以使用任何值类型的响应式 ref。

4.3.1 用 ref() 定义响应式变量

import { ref } from "vue";

const count = ref(0);

ref() 方法将传入的参数的值,包装成一个带 .value 属性的 ref 对象:

const count = ref(0);

console.log(count); // { value: 0 }
console.log(count.value); // 0

count.value++;
console.log(count.value); // 1

和响应式对象的属性类似,ref 的 .value 属性也是响应式的。

4.3.2 响应式 ref 的优势

  1. 可作用于所有值:包括原始值和对象

  2. 一个包含对象类型值的 ref 可以响应式地替换整个对象

  3. ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性

const objectRef = ref({ count: 0 });

objectRef.value = { count: 1 }; // 替换后,依然是响应式的
const obj = {
  foo: ref(1),
  bar: ref(2)
};

callSomeFunction(obj.foo); // 它会保持响应性

const { foo, bar } = obj; // 仍然是响应式的

简而言之,ref() 让我们能创造一种对任意值的“引用”,并能在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到组合式函数中。

4.3.3 响应式 ref 的解包

解包,即不需要写 .value

  1. ref 在模板中的解包:仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”

  2. ref 在响应式对象中的解包:只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包

  3. 数组和集合类型的 ref 解包:跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包

  4. Vue 提供了一种编译时转换,可以让编译器帮我们省去使用写 .value 的麻烦,详见响应性语法糖

4.4 计算属性

计算属性可以用来描述依赖响应式状态的复杂逻辑。

<script setup>
  import { reactive, computed } from "vue";

  const author = reactive({
    name: "John Doe",
    books: [
      "Vue 2 - Advanced Guide",
      "Vue 3 - Basic Guide",
      "Vue 4 - The Mystery"
    ]
  });

  // 一个计算属性 ref
  const publishedBooksMessage = computed(() => {
    return author.books.length > 0 ? "Yes" : "No";
  });
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。

  1. 计算属性 ref 会在模板中自动解包,所以在模板表达式中引用时无需添加 .value

  2. 计算属性会自动追踪响应式依赖

    • 它会检测到依赖于 author.books,所以当 author.books 改变时,任何依赖于 publishedBooksMessage 的绑定都会同时更新

  3. 计算属性 vs 方法

    • 一个计算属性仅会在其响应式依赖更新时才重新计算,即值会被缓存

    • 方法调用总是会在重渲染发生时再次执行函数

在某些特殊场景中,我们可以通过同时提供 getter 和 setter 来创建 computed()

<script setup>
  import { ref, computed } from "vue";

  const firstName = ref("John");
  const lastName = ref("Doe");

  const fullName = computed({
    get() {
      return firstName.value + " " + lastName.value;
    },
    set(newValue) {
      [firstName.value, lastName.value] = newValue.split(" ");
    }
  });
</script>

最佳实践:

  1. 计算函数不应有副作用。一个计算属性的声明中描述的是如何根据其他值派生一个值

  2. 避免直接修改计算属性值。应该更新它所依赖的源状态以触发新的计算

4.5 有副作用的状态变化

计算属性能让我们声明性地计算衍生值。但有时,需要在响应式状态变化时执行一些副作用,比如更改 DOM,比如根据异步操作的结果去修改另一处的状态。

此时,我们可以使用 watchwatchEffect,它们都能响应式地执行有副作用的回调。

const obj = reactive({ count: 0 });

watch(obj, (newValue, oldValue) => {});

obj.count++;

watchwatchEffect 的区别如下:

  1. 执行时机

    • watch 是懒执行的。仅当数据源变化时,才会执行回调

    • watchEffect 会在创建侦听器时,立即执行一遍回调。

  2. 追踪响应式依赖的方式(主要区别)

    • watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。

    • watchEffect 则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。

更多内容:

  1. 回调的触发时机

    • Vue 更新之前的 DOM:默认情况,侦听器回调是在 Vue 组件更新之前被调用,也就是说在侦听器回调中访问的 DOM 是被 Vue 更新之前的状态

    • Vue 更新之后的 DOM:需增加选项 flush: 'post',后置刷新有个别名 watchPostEffect

  2. 回调:同步 vs 异步

    • watchEffect 仅会在其同步执行期间追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪

  3. 深层侦听器(注意性能问题)

  4. 必须用同步语句创建侦听器(关键点)

    • 用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止

    • 若是用异步回调创建的侦听器,那么它不会绑定到当前组件上,我们必须手动停止它以防内存泄漏

/**
 * watch() 的第一个参数可以是不同形式的“数据源”:
 *   1. 一个 ref (包括计算属性)
 *   2. 一个响应式对象
 *   3. 一个 getter 函数
 *   4. 多个数据源组成的数组
 */
const x = ref(0);
const y = ref(0);

// 1. 单个 ref
watch(x, newX => {
  console.log(`x is ${newX}`);
});

// 3. getter 函数
watch(
  () => x.value + y.value,
  sum => {
    console.log(`sum of x + y is: ${sum}`);
  }
);

// 4. 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`);
});

5. 生命周期

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,在此期间它也会运行被称为生命周期钩子的函数,以便让开发者有机会在特定阶段运行自己的代码。

实例的生命周期的图表,如下:

  1. 渲染遇到了组件

    • setup 组合式 API

    • beforeCreate

  2. 设置好数据侦听(初始化选项 API)

    • created

  3. 编译模板

  4. 开始渲染(插入到 DOM 树)

    • beforeMount

    • mounted

  5. 挂载实例到 DOM(mounted)

  6. 在数据改变时更新 DOM

    • beforeUpdate

    • updated

  7. 卸载 unmounted

    • beforeUnmount

    • unmounted

最常用的是 onMounted, onUpdatedonUnmounted

所有生命周期钩子的完整参考及其用法请参考生命周期钩子 API

6. 组件

6.1 组件模型

Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。Vue 同样也能很好地配合原生 Web Component,详见 Vue 与 Web Components

  1. 定义组件

    1. 使用构建:将 Vue 组件定义在一个单独的 .vue 文件中(单文件组件,简称 SFC)

    2. 不使用构建:一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义

  2. 使用组件

    • 在父组件中导入它 or 全局地注册一个组件。详见全局注册 vs 局部注册

    • 组件可以被重用任意多次。每当使用一个组件,就创建了一个新的实例

    • 组件的标签名:推荐用驼峰 PascalCase,以和原生 HTML 元素作区分

  3. 传递 props

    1. props 是一种特别的 attributes,用宏 defineProps 定义组件自己的属性

      • defineProps 是一个仅 <script setup> 中可用的编译宏命令,并不需要显式地导入

    2. 使用时,就像使用自定义 attribute 一样 <BlogPost :title="xx"/>

  4. 监听事件

    1. 父组件可以通过 v-on@ 来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样

    2. 子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件

  5. 插槽 <slot>:作为一个占位符,来显示父组件传进来的内容

  6. 动态组件:在两个组件间来回切换,<component :is="...">

  7. 直接在 DOM 中编写模板

<!-- BlogPost.vue -->
<script setup>
  defineProps(["title"]);
</script>

<template>
  <h4>{{ title }}</h4>
</template>

6.2 内置组件

  1. 帮助制作基于状态变化的过渡和动画(更多动画技巧

    • <Transition>

    • <TransitionGroup>

  2. 在多个组件间动态切换时缓存被移除的组件实例:<KeepAlive>

    • 在两个组件间来回切换,比如 Tab 界面

  3. 将组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去:<Teleport>

    • 比如全屏的模态框

  4. 在组件树中协调对异步依赖的处理:<Suspense>

7. 路由

在这单页应用中,“路由”是在客户端执行的,利用诸如 History API 或是 hashchange 事件这样的浏览器 API 来管理应用当前应该渲染的视图。

Vue 提供了官方的路由库 vue-router

Last updated