Vue3 官方文档速通

219 阅读38分钟

前言:参考Vue 官方文档,本文档主要学习组合式 API。

一 开始

1. 简介

1.1 什么是 Vue?

一款 JS 框架,并有两个核心功能:声明式渲染、响应性。

 1.2 渐进式框架

根据不同的需求场景,使用不同方式的 Vue,比如:

        无需构建步骤,直接引入 vuejs。

        在任何页面中作为 Web Components 嵌入

        使用构建步骤,单页应用 (SPA)

        全栈 / 服务端渲染 (SSR)

        Jamstack / 静态站点生成 (SSG)

        开发桌面端、移动端、WebGL,甚至是命令行终端中的界面

Vue 为什么可以称为“渐进式框架”:它是一个可以与你共同成长、适应你不同需求的框架。

 1.3 单文件组件

单文件组件是 Vue 的标志性功能。*.vue、SFC 就是单文件组件:将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。

 1.4 API 风格

选项式 API(Options API):包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。

组合式 API(Composition API):使用导入的 API 函数来描述组件逻辑。通常会与

综上:两种 API 是同一个底层系统构建的。选项式 API 是在组合式 API 的基础上实现的!

 2. 快速上手

2.1 创建一个 Vue 应用

// 安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。
npm create vue@latest

2.2 通过 CDN 使用 Vue

可以用于增强静态的 HTML 或与后端框架集成。但将无法使用单文件组件 (SFC) 语法:

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<div id="app">{{ message }}</div>

<script>
  const { createApp, ref } = Vue;

  createApp({
    setup() {
      const message = ref("Hello vue!");
      return {
        message,
      };
    },
  }).mount("#app");
</script>

通过 CDN 以及原生 ES 模块使用 Vue:

<div id="app">{{ message }}</div>

<script type="module">
  import {
    createApp,
    ref,
  } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";

  createApp({
    setup() {
      const message = ref("Hello Vue!");
      return {
        message,
      };
    },
  }).mount("#app");
</script>

使用导入映射表 (Import Maps) 来告诉浏览器如何定位到导入的 vue:

<script type="importmap">
  {
    "imports": {
      "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
    }
  }
</script>

<div id="app">{{ message }}</div>

<script type="module">
  import { createApp, ref } from "vue";

  createApp({
    setup() {
      const message = ref("Hello Vue!");
      return {
        message,
      };
    },
  }).mount("#app");
</script>

分割代码成单独的 JS 文件,以便管理。

<!-- index.html -->
<div id="app"></div>

<script type="module">
  import { createApp } from "vue";
  import MyComponent from "./my-component.js";

  createApp(MyComponent).mount("#app");
</script>

// my-component.js
import { ref } from "vue";
export default {
  setup() {
    const count = ref(0);
    return { count };
  },
  template: `<div>count is {{ count }}</div>`,
};

注意:直接点击 index.html,会抛错误,因为 ES 模块不能通过 file:// 协议工作。只能通过 http:// 协议工作。需要启动一个本地的 HTTP 服务器,通过命令行在 HTML 文件所在文件夹下运行 npx serve。

 二 基础

1. 创建一个 Vue 应用

1.1 应用实例

通过 createApp 函数创建 Vue 应用实例:

import { createApp } from "vue";

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

1.2 根组件

createApp 需要传入一个根组件,其他组件将作为其子组件:

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

const app = createApp(App);

1.3 挂载应用

调用 .mount() 方法,传入一个 DOM 元素或是 CSS 选择器。它的返回值是根组件实例而非应用实例:

<div id="app"></div>

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

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

1.4 应用配置

注意:确保在挂载应用实例之前完成所有应用配置!

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

const app = createApp(App);

// 应用实例的 .config 对象可以进行一些配置,例如配置错误处理器:用来捕获所有子组件上的错误:
app.config.errorHandler = (err) => {
  /* 处理错误 */
};

// 全局挂载组件
app.component("TodoDeleteButton", TodoDeleteButton);

// 全局属性的对象。
app.config.globalProperties.msg = "hello";

app.mount("#app");

1.5 多个应用实例

每个应用都拥有自己的用于配置和全局资源的作用域:

const app1 = createApp({
  /* ... */
});
app1.mount("#container-1");

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

2. 模板语法

2.1 文本插值

最基本的数据绑定是文本插值,使用“Mustache”语法 (即双大括号):

<span>Message: {{ msg }}</span>

2.2 原始 HTML

双大括号会将数据解释为纯文本,若想插入 HTML,需要使用 v-html 指令。

安全警告:动态渲染 HTML 是很危险的,容易造成 XSS 漏洞。仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容。

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

2.3 Attribute 绑定

绑定 attribute,使用 v-bind 指令:

<div v-bind:id="dynamicId"></div>
<!-- 简写 -->
<div :id="dynamicId"></div>

绑定多个值,通过不带参数的 v-bind。

const objectOfAttrs = {
  id: "container",
  class: "wrapper",
};

<div v-bind="objectOfAttrs"></div>

2.4 使用 JS 表达式

数据绑定都支持完整的 JS 表达式,也就是一段能够被求值的 JS 代码。一个简单的判断方法是是否可以合法地写在 return 后面:

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

可以在绑定的表达式中使用一个组件暴露的方法:

<time :title="toTitleDate(date)" :datetime="date">
  {{ formatDate(date) }}
</time>

2.5 指令 Directives

指 v- 前缀的特殊 attribute。它的值为 JS 表达式(v-for、v-on、v-slot 除外),指令值变化时响应式的更新 DOM,比如 v-if:

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

带参数的指令,用':'隔开:

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

指令的参数,也可以动态绑定,用 '[ ]' 包裹:

<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

带修饰符的指令,用 '.' 隔开:

<!-- 触发的事件调用 event.preventDefault() -->
<form @submit.prevent="onSubmit">...</form>

3. 响应式基础

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。ref 会自动解包。也可以直接在事件监听器中改变一个 ref:

<button @click="count++">{{ count }}</button>

通过单文件组件(SFC),使用

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

const count = ref(0);

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

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

为什么使用 ref,而不是普通的变量。这是因为 Vue 需要通过.value 属性来实现状态响应性。基础原理是在 getter 中追踪,在 setter 中触发:

// 伪代码,不是真正的实现
const myRef = {
  _value: 0,
  get value() {
    track();
    return this._value;
  },
  set value(newValue) {
    this._value = newValue;
    trigger();
  },
};

要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:

import { nextTick } from "vue";

async function increment() {
  count.value++;
  await nextTick();
  // 现在 DOM 已经更新了
}

3.2 reactive()

reactive(),参数只能是对象类型,返回的是一个原始对象的 Proxy,它和原始对象是不相等的:

const raw = {};
const proxy = reactive(raw);

// 代理对象和原始对象不是全等的
console.log(proxy === raw); // false

reactive() 局限性包括:只能用于对象类型(对象,数组,Map,Set)、不能替换整个对象、对结构操作不友好:

let state = reactive({ count: 0 });
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 });

// 对解构不友好
const state = reactive({ count: 0 });
// 当解构时,count 已经与 state.count 断开连接
let { count } = state;
// 不会影响原始的 state
count++;
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count);

reactive() API 有一些局限性,官方建议使用 ref() 作为声明响应式状态的主要 API。博主个人还是喜欢 ref,reactive 混着用,注意那些局限性就可以了。

 3.3 额外的 ref 解包细节

一个 ref 会在作为响应式对象的属性被访问或修改时自动解包:

const count = ref(0);
const state = reactive({
  count,
});

console.log(state.count); // 0

state.count = 1;
console.log(count.value); // 1

 4. 计算属性

4.1 基础示例

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

<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>

4.2 计算属性缓存 vs 方法

计算属性值会基于其响应式依赖被缓存。方法调用则总会在重新渲染时再次执行。

 4.3 可写计算属性

计算属性默认是只读的。但可以通过设置 get 和 set 函数变成可读可写:

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

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

const fullName = computed({
  // getter
  get() {
    return firstName.value + " " + lastName.value;
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(" ");
  },
});
</script>

4.4 最佳实践

使用计算属性,不要在里面做异步请求和修改 DOM。并且尽量保持只读。

5. 类与样式绑定

5.1 绑定 HTML class

通过对象来动态切换 class:

<div :class="{ active: isActive }"></div>

可以直接绑定一个对象:

const classObject = reactive({
  active: true,
  "text-danger": false,
});

<div :class="classObject"></div>

通过数组渲染多个 class:

const activeClass = ref("active");
const errorClass = ref("text-danger");

<div :class="[activeClass, errorClass]"></div>

数组中也可以使用 JS 表达式:

<div :class="[isActive ? activeClass : '', errorClass]"></div>
<!-- 等于 -->
<div :class="[{ activeClass: isActive }, errorClass]"></div>

如果组件有多个根元素,透传的 class 需要通过组件的 $attrs 属性来实现指定:

<MyComponent class="baz" />

<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>

<p class="baz">Hi!</p>
<span>This is a child component</span>

5.2 绑定内联样式

值为对象,对应的是 style 属性:

const activeColor = ref("red");
const fontSize = ref(30);

<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

直接绑定一个样式对象使模板更加简洁:

const styleObject = reactive({
  color: "red",
  fontSize: "13px",
});

<div :style="styleObject"></div>

还可以绑定一个包含多个样式对象的数组:

<div :style="[baseStyles, overridingStyles]"></div>

6. 条件渲染

6.1 v-if、v-else、v-else-if

v-if 指令用于条件性地渲染内容。当值为真时才被渲染:

<h1 v-if="awesome">Vue is awesome!</h1>

v-else 为 v-if 添加一个“else 区块”。并且必须跟在一个 v-if 或者 v-else-if 元素后面:

<button @click="awesome = !awesome">Toggle</button>

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

v-else-if 提供的是相应于 v-if 的“else if 区块”。可以连续多次使用。并且必须跟在一个 v-if 或者 v-else-if 元素后面:

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

可以使用