Vue 3 选项式 API 核心知识点与实战(十五)

Vue 3 作为现代前端框架中的佼佼者,其选项式 API 为开发者提供了强大的工具,帮助他们高效地管理和构建复杂的应用程序。本文将详细介绍 Vue 3 选项式 API 的核心知识点,并通过一个任务管理系统项目来巩固这些知识。通过本文的学习,你将能够熟练掌握 Vue 3 的各种选项式 API,并了解如何在实际项目中应用它们。

1. 状态管理选项

在 Vue 3 中,状态管理是构建响应式应用程序的基础。以下是几个常用的选项式 API,用于管理组件的状态:

1.1 data - 组件的数据选项

data 选项用于定义组件的初始状态。这些状态是响应式的,当它们发生变化时,Vue 会自动更新视图。

data() {
  return {
    count: 0
  };
}

1.2 props - 父组件传递的数据

props 选项用于接收父组件传递的数据。这些数据也是响应式的,可以在子组件中使用。

props: {
  msg: String
}

1.3 computed - 计算属性

计算属性用于定义基于组件状态(data、props)的派生值。计算属性是响应式的,当依赖的数据发生变化时,计算属性会自动重新计算。

computed: {
  doubledCount() {
    return this.count * 2;
  }
}

1.4 methods - 组件的方法

methods 选项用于定义组件的业务逻辑。这些方法可以在模板中通过事件处理器调用。

methods: {
  increment() {
    this.count += 1;
  }
}

1.5 watch - 监听数据变化

watch 选项用于监听组件的响应式数据变化,并执行回调函数。这在处理复杂的状态变化时非常有用。

watch: {
  count(newVal) {
    console.log(newVal);
  }
}

1.6 emits - 自定义事件

emits 选项用于定义组件可以触发的自定义事件。这在父子组件通信中非常常见。

emits: ['update']

1.7 expose - 显式暴露属性或方法

expose 选项用于显式暴露给父组件访问的属性或方法。这在需要从父组件直接调用子组件的方法时非常有用。

expose() {
  return { method };
}

2. 渲染选项

渲染选项用于定义组件的模板和渲染逻辑。以下是几个常用的渲染选项:

2.1 template - 定义组件的 HTML 模板

template 选项用于定义组件的 HTML 模板。这是最常见的渲染方式。


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

2.2 render - 使用渲染函数

render 选项用于使用渲染函数替代模板。这在需要更复杂的渲染逻辑时非常有用。

render(h) {
  return h('button', { on: { click: this.increment } }, this.count);
}

2.3 compilerOptions - 配置模板编译器

compilerOptions 选项用于配置模板编译器的选项,例如自定义插值符号。

compilerOptions: {
  delimiters: ['{{', '}}']
}

2.4 slots - 定义组件的插槽

slots 选项用于定义组件的插槽。插槽允许你在组件中插入内容,提高组件的复用性。


<slot></slot>

3. 生命周期选项

生命周期选项用于在组件的不同生命周期阶段执行特定的操作。以下是几个常用的生命周期钩子:

3.1 beforeCreate - 组件实例化之前

beforeCreate 钩子在组件实例化之前被调用。此时,组件的数据尚未初始化。

beforeCreate() {
  console.log('Before Create');
}

3.2 created - 组件实例已创建

created 钩子在组件实例已创建后调用。此时,数据已经设置,但组件尚未挂载到 DOM。

created() {
  console.log('Created');
}

3.3 beforeMount - 挂载到 DOM 前

beforeMount 钩子在组件挂载到 DOM 前被调用。此时,模板已编译,但尚未挂载。

beforeMount() {
  console.log('Before Mount');
}

3.4 mounted - 组件挂载到 DOM 后

mounted 钩子在组件挂载到 DOM 后被调用。此时,可以访问到 DOM 元素。

mounted() {
  console.log('Mounted');
}

3.5 beforeUpdate - 数据变化后、组件重新渲染前

beforeUpdate 钩子在数据变化后、组件重新渲染前被调用。此时,可以进行一些预处理操作。

beforeUpdate() {
  console.log('Before Update');
}

3.6 updated - 组件更新完成后

updated 钩子在组件更新完成后被调用。此时,DOM 已经更新。

updated() {
  console.log('Updated');
}

3.7 beforeUnmount - 组件卸载之前

beforeUnmount 钩子在组件卸载之前被调用。此时,可以进行一些清理操作。

beforeUnmount() {
  console.log('Before Unmount');
}

3.8 unmounted - 组件卸载后

unmounted 钩子在组件卸载后被调用。此时,组件已经完全销毁。

unmounted() {
  console.log('Unmounted');
}

3.9 errorCaptured - 捕获子组件错误

errorCaptured 钩子在捕获子组件错误时被调用。这有助于集中处理错误。

errorCaptured(err, vm, info) {
  console.error(err);
}

3.10 renderTracked - 响应式数据变化时

renderTracked 钩子在响应式数据变化并且重新渲染时被调用。这有助于调试性能问题。

renderTracked(event) {
  console.log('Render Tracked');
}

3.11 renderTriggered - 响应式数据触发重新渲染时

renderTriggered 钩子在响应式数据触发重新渲染时被调用。这也有助于调试性能问题。

renderTriggered(event) {
  console.log('Render Triggered');
}

3.12 activated - 组件被激活时

activated 钩子在组件被激活时被调用。这仅适用于 keep-alive 组件。

activated() {
  console.log('Activated');
}

3.13 deactivated - 组件被停用时

deactivated 钩子在组件被停用时被调用。这仅适用于 keep-alive 组件。

deactivated() {
  console.log('Deactivated');
}

3.14 serverPrefetch - 服务器端渲染时

serverPrefetch 钩子在服务器端渲染时获取数据。这有助于提高首屏加载速度。

serverPrefetch() {
  return fetchData();
}

4. 组合选项

组合选项用于实现组件之间的依赖注入和逻辑复用。以下是几个常用的组合选项:

4.1 provide - 提供依赖

provide 选项用于提供依赖给后代组件。这在需要跨层级传递数据时非常有用。

provide('key', 'value');

4.2 inject - 注入依赖

inject 选项用于从祖先组件中注入依赖。这在需要跨层级获取数据时非常有用。

inject('key');

4.3 mixins - 引入外部混入

mixins 选项用于引入外部混入,以便复用逻辑。这在多个组件共享相同逻辑时非常有用。

mixins: [myMixin];

4.4 extends - 组件扩展

extends 选项用于扩展另一个组件的选项。这在需要继承现有组件时非常有用。

extends: MyComponent;

5. 其他杂项

除了上述选项外,还有一些其他杂项选项用于配置组件的行为。以下是几个常用的杂项选项:

5.1 name - 组件的名称

name 选项用于定义组件的名称,通常用于调试。

name: 'MyComponent';

5.2 inheritAttrs - 控制属性继承

inheritAttrs 选项用于控制是否将父级组件的属性(attrs)自动继承到根元素。

inheritAttrs: false;

5.3 components - 注册局部组件

components 选项用于注册局部组件,使其可以在当前组件中使用。

components: { LocalComponent };

5.4 directives - 注册局部指令

directives 选项用于注册局部指令,使其可以在当前组件中使用。

directives: { focus: FocusDirective };

6. 组件实例

组件实例提供了许多方法和属性,用于访问和操作组件的状态和行为。以下是几个常用的组件实例 API:

6.1 $data - 获取组件的数据对象

$data 属性用于获取组件的数据对象。

this.$data.count;

6.2 $props - 获取组件的 props 对象

$props 属性用于获取组件的 props 对象。

this.$props.msg;

6.3 $el - 获取组件挂载的 DOM 元素

$el 属性用于获取组件挂载的 DOM 元素。

this.$el;

6.4 $options - 获取组件的配置选项

$options 属性用于获取组件的配置选项。

this.$options.name;

6.5 $parent - 获取父组件的实例

$parent 属性用于获取父组件的实例。

this.$parent;

6.6 $root - 获取根组件的实例

$root 属性用于获取根组件的实例。

this.$root;

6.7 $slots - 获取组件的插槽内容

$slots 属性用于获取组件的插槽内容。

this.$slots.default;

6.8 $refs - 获取组件的引用对象

$refs 属性用于获取组件的引用对象。

this.$refs.input;

6.9 $attrs - 获取所有未绑定到组件 props 的属性

$attrs 属性用于获取所有未绑定到组件 props 的属性。

this.$attrs;

6.10 $watch - 监视组件实例的响应式数据或计算属性

$watch 方法用于监视组件实例的响应式数据或计算属性。


this.$watch('count', (newCount) => {
  console.log(newCount);
});

6.11 $emit - 触发一个自定义事件

$emit 方法用于触发一个自定义事件。

this.$emit('update', newVal);

6.12 $forceUpdate - 强制 Vue 重新渲染组件

$forceUpdate 方法用于强制 Vue 重新渲染组件。

this.$forceUpdate();

6.13 $nextTick - 在下次 DOM 更新循环结束之后执行回调

$nextTick 方法用于在下次 DOM 更新循环结束之后执行回调。

this.$nextTick(() => {
  console.log('DOM updated');
});

实战项目:任务管理系统

接下来,我们将通过创建一个 Vue 3 任务管理系统来巩固之前学习到的知识点。这个项目将涵盖以下几个核心功能:

  • 任务看板:展示任务列表,支持添加、删除和编辑任务。
  • 分类筛选:支持按类别筛选任务。
  • 数据持久化:使用 LocalStorage 保存任务数据。
  • 深色模式切换:支持用户切换深色模式。
  • 响应式设计:确保在不同设备上都能良好显示。

技术栈

  • Vue 3 (Script Setup):使用最新的组合式 API。
  • Vite:快速构建工具。
  • Pinia:状态管理库,替代 Vuex。
  • Vue Router:路由管理库。
  • Element Plus:UI 组件库。
  • Tailwind CSS 4.1:现代前端最流行的样式解决方案。

项目架构与环境准备

1. 创建项目

打开终端,执行以下命令创建 Vite + Vue3 项目(选择 Script Setup 语法):

npm create vite@latest task-hub -- --template vue

yarn create vite task-hub --template vue

pnpm create vite task-hub -- --template vue

执行成功后,启动项目:

npm install
npm run dev

启动成功后,访问终端提示的本地地址(默认 http://localhost:5173/),即可看到 Vue3 初始页面。

2. 项目目录

进入项目目录:

cd task-hub

如果安装了 VS Code,可以使用 VS Code 的 code 命令打开目录:

code .

3. 清理默认代码

删除 src/components/HelloWorld.vue。

清空 src/style.css。

修改 src/App.vue,代码如下:

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

const newTaskTitle = ref('');
const tasks = ref([]);
const currentFilter = ref('all');

const addTask = () => {
  if (newTaskTitle.value.trim()) {
    tasks.value.push({ id: Date.now(), title: newTaskTitle.value, completed: false });
    newTaskTitle.value = '';
  }
};

const filteredTasks = computed(() => {
  if (currentFilter.value === 'completed') {
    return tasks.value.filter(task => task.completed);
  } else if (currentFilter.value === 'active') {
    return tasks.value.filter(task => !task.completed);
  } else {
    return tasks.value;
  }
});

const toggleTask = (id) => {
  const task = tasks.value.find(task => task.id === id);
  if (task) {
    task.completed = !task.completed;
  }
};
</script>

<template>
  <div class="container mx-auto p-4">
    <h1 class="text-2xl font-bold mb-4">任务管理系统</h1>
    <input v-model="newTaskTitle" @keyup.enter="addTask" class="w-full p-2 border rounded" placeholder="添加新任务">
    <ul class="mt-4">
      <li v-for="task in filteredTasks" :key="task.id" class="p-2 border-b flex justify-between items-center">
        <span :class="{ 'line-through': task.completed }">{{ task.title }}</span>
        <button @click="toggleTask(task.id)" class="bg-blue-500 text-white px-2 py-1 rounded">{{ task.completed ? '完成' : '未完成' }}</button>
      </li>
    </ul>
    <div class="mt-4">
      <button @click="currentFilter = 'all'" :class="{ 'bg-blue-500 text-white': currentFilter === 'all' }" class="px-2 py-1 rounded mr-2">全部</button>

      <button @click="currentFilter = 'active'" :class="{ 'bg-blue-500 text-white': currentFilter === 'active' }" class="px-2 py-1 rounded mr-2">未完成</button>
      <button @click="currentFilter = 'completed'" :class="{ 'bg-blue-500 text-white': currentFilter === 'completed' }" class="px-2 py-1 rounded">已完成</button>
    </div>
  </div>
</template>

<style>
@import "tailwindcss";

/* v4 定义主题变量的方式 */
@theme {
  --color-brand: #3b82f6;
  --radius-xl: 1rem;
}

/* 全局基础样式 */
@layer base {
  body {
    @apply bg-slate-50 text-slate-900 antialiased;
  }
}
</style>

核心知识点

1. 组合式 API (Composition API)

以上代码展示了 Vue 3 最核心的逻辑组织方式:

  • ref (响应式基础):tasks、newTaskTitle 等都是响应式引用。在 <script> 中修改它们必须使用 .value(如 tasks.value = ...),但在 <template> 中直接写变量名。Vue 会自动追踪这些值的变化并更新 UI。
  • computed (计算属性):filteredTasks 是一个非常经典的应用。它基于 tasks 和 currentFilter 派生而来。
  • 优势:它是响应式的,且具有缓存性。如果 tasks 没变,多次渲染页面时,它不会重复执行过滤逻辑。

2. 列表渲染与 Key 值

<li v-for="task in filteredTasks" :key="task.id">
  • :key 的重要性:key 属性用于帮助 Vue 识别哪些元素发生了变化,从而高效地更新 DOM。每个列表项都应该有一个唯一的 key,以确保 Vue 能够正确地跟踪和更新列表。

总结

通过本文的学习,你已经掌握了 Vue 3 选项式 API 的核心知识点,并了解了如何在实际项目中应用它们。通过创建一个任务管理系统,你不仅巩固了这些知识,还学会了如何使用现代前端技术栈构建复杂的应用程序。希望本文对你有所帮助,祝你在 Vue 3 的开发之旅中取得更大的进步!