Vue 组合式 API(Composition API)基础(Vue 3 核心)详解
【摘要】 一、引言在 Vue.js 的发展历程中,选项式 API(Options API) 一直是核心的开发模式(如 Vue 2 的 data、methods、computed等选项)。但随着应用复杂度的提升,选项式 API 逐渐暴露出一些局限性:逻辑复用困难:相关逻辑(如数据获取、事件处理)分散在 data、methods、computed等不同选项中,难以提取和复用;代码组织不...
一、引言
data
、methods
、computed
等选项)。但随着应用复杂度的提升,选项式 API 逐渐暴露出一些局限性:-
逻辑复用困难:相关逻辑(如数据获取、事件处理)分散在 data
、methods
、computed
等不同选项中,难以提取和复用; -
代码组织不直观:大型组件中,选项式 API 的代码按功能类型拆分(如 data
定义数据、methods
定义方法),但实际业务逻辑可能是跨功能的(如“用户信息管理”涉及数据、操作、计算),导致代码可读性和维护性降低; -
TypeScript 支持较弱:选项式 API 的类型推断需要额外配置,对 TypeScript 的友好度不足。
二、技术背景
1. 选项式 API 的局限性
-
data
:定义响应式数据(如用户信息、表单输入值); -
methods
:定义组件的方法(如事件处理函数、数据操作函数); -
computed
:定义计算属性(基于其他数据动态计算的值); -
watch
:监听数据变化并执行副作用(如异步请求、DOM 操作); -
生命周期钩子
(如created
、mounted
):在组件不同阶段执行逻辑。
export default {
data() {
return {
user: null, // 用户数据
loading: false, // 加载状态
error: null, // 错误信息
};
},
methods: {
async fetchUser() { // 获取用户数据(方法)
this.loading = true;
try {
const res = await api.getUser(); // 假设的 API 调用
this.user = res.data;
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
},
submitForm() { /* 提交表单逻辑 */ }
},
created() { // 生命周期钩子
this.fetchUser(); // 组件创建时获取用户
}
};
-
相关逻辑(如 fetchUser
方法、loading
/error
状态、created
钩子中的调用)分散在不同选项中,难以快速定位和复用; -
若需要在多个组件中复用“获取用户数据”的逻辑,需通过 Mixin 或高阶组件实现,但会导致命名冲突和类型推断困难。
2. 组合式 API 的核心思想
-
基于函数的 API:提供 ref
、reactive
、computed
、watch
、onMounted
等函数,用于定义响应式数据、计算属性、监听器和生命周期逻辑; -
逻辑聚合:将相关的功能封装到一个或多个自定义函数(称为“组合函数”)中,组件只需调用这些函数即可复用逻辑; -
更好的 TypeScript 支持:函数的参数和返回值具有明确的类型,能自动推断组件的状态和方法类型; -
更灵活的代码组织:开发者可以根据业务逻辑(如“用户管理”“表单处理”)而非选项类型(如 data
/methods
)组织代码,提升可读性。
三、应用使用场景
1. 复杂组件的逻辑复用
-
场景:多个组件需要共享相同的功能(如“用户认证”“数据分页”“表单验证”); -
需求:通过组合式 API 将这些功能封装成独立的组合函数(如 useAuth
、usePagination
),在需要的组件中直接调用,避免重复代码。
2. 大型组件的代码组织
-
场景:单个组件包含大量逻辑(如电商详情页包含商品信息、库存管理、用户评价、购物车操作); -
需求:将不同功能的逻辑(如商品数据获取、评价列表渲染、购物车更新)拆分成多个组合函数,每个函数专注单一职责,提升代码可维护性。
3. TypeScript 项目的类型安全
-
场景:使用 TypeScript 开发 Vue 应用,需要明确的类型推断和类型检查; -
需求:组合式 API 的函数(如 ref<T>
、reactive<T>
)支持泛型,能自动推断组件状态的类型,减少类型定义的冗余代码。
4. 逻辑跨组件复用
-
场景:多个组件需要使用相同的工具逻辑(如“窗口大小监听”“本地存储管理”“API 请求封装”); -
需求:通过组合函数(如 useWindowSize
、useLocalStorage
)封装这些工具逻辑,在不同组件中复用,避免重复实现。
四、不同场景下详细代码实现
场景 1:基础响应式数据与生命周期(Vue 3 组合式 API)
Counter.vue
),通过组合式 API 定义响应式数据(count
)、方法(increment
)和生命周期钩子(onMounted
)。1.1 计数器组件(Counter.vue)
<template>
<div class="counter">
<h3>组合式 API 基础示例(计数器)</h3>
<p>当前计数:{{ count }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// 定义响应式数据(类似 Options API 的 data)
const count = ref(0); // 初始值为 0
// 定义方法(类似 Options API 的 methods)
const increment = () => {
count.value++; // ref 的值需通过 .value 访问
};
const decrement = () => {
count.value--;
};
// 生命周期钩子(类似 Options API 的 mounted)
onMounted(() => {
console.log('✅ 组件已挂载到 DOM,当前计数:', count.value);
});
</script>
<style scoped>
.counter {
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
margin: 10px;
}
button {
margin: 0 8px;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
-
页面显示一个计数器,点击“+1”或“-1”按钮时, count
的值实时更新; -
打开浏览器控制台(F12),组件挂载后输出日志 ✅ 组件已挂载到 DOM,当前计数:0
。
场景 2:逻辑复用(自定义组合函数)
useUser.js
),在多个组件中复用获取用户数据的逻辑(如用户详情页、用户列表页)。2.1 自定义组合函数(useUser.js)
// src/composables/useUser.js
import { ref, onMounted } from 'vue';
// 定义组合函数:封装获取用户数据的逻辑
export function useUser() {
// 响应式数据:用户信息和加载状态
const user = ref(null);
const loading = ref(false);
const error = ref(null);
// 方法:获取用户数据(模拟 API 调用)
const fetchUser = async (userId) => {
loading.value = true;
error.value = null;
try {
// 模拟异步请求(实际项目中替换为真实 API 调用)
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({ data: { id: userId, name: `用户${userId}`, age: 20 + userId } });
}, 1000);
});
user.value = response.data;
} catch (err) {
error.value = '获取用户数据失败:' + err.message;
} finally {
loading.value = false;
}
};
// 生命周期:组件挂载时自动获取用户(可选,根据需求调用)
// 注意:实际使用时,通常由组件手动调用 fetchUser(userId)
// 返回响应式数据和方法,供组件使用
return {
user,
loading,
error,
fetchUser,
};
}
2.2 使用组合函数的组件(UserDetail.vue)
<template>
<div class="user-detail">
<h3>用户详情(使用组合函数 useUser)</h3>
<button @click="loadUser(1)">加载用户 1</button>
<button @click="loadUser(2)">加载用户 2</button>
<!-- 加载状态 -->
<p v-if="loading">加载中...</p>
<!-- 错误提示 -->
<p v-else-if="error" style="color: red;">错误:{{ error }}</p>
<!-- 用户信息 -->
<div v-else-if="user">
<p><strong>ID:</strong>{{ user.id }}</p>
<p><strong>姓名:</strong>{{ user.name }}</p>
<p><strong>年龄:</strong>{{ user.age }}</p>
</div>
<!-- 未加载时的提示 -->
<p v-else>点击按钮加载用户信息</p>
</div>
</template>
<script setup>
import { useUser } from '@/composables/useUser'; // 导入自定义组合函数
// 调用组合函数,获取响应式数据和方法
const { user, loading, error, fetchUser } = useUser();
// 组件内定义方法:触发用户数据获取
const loadUser = (userId) => {
fetchUser(userId); // 调用组合函数中的方法
};
</script>
<style scoped>
.user-detail {
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
margin: 10px;
}
button {
margin: 0 8px 10px 0;
padding: 8px 16px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
-
点击“加载用户 1”或“加载用户 2”按钮,触发 fetchUser
方法,模拟异步请求后显示用户信息(ID、姓名、年龄); -
加载过程中显示“加载中...”,出错时显示错误提示,逻辑完全复用自 useUser
组合函数。
场景 3:计算属性与监听器(组合式 API)
Cart.vue
),通过组合式 API 的 computed
(计算属性)计算总价,通过 watch
(监听器)监听商品列表变化并输出日志。3.1 购物车组件(Cart.vue)
<template>
<div class="cart">
<h3>购物车(组合式 API:计算属性与监听器)</h3>
<div v-for="item in cartItems" :key="item.id" class="cart-item">
<span>{{ item.name }} - ¥{{ item.price }} × {{ item.quantity }}</span>
<button @click="updateQuantity(item.id, item.quantity + 1)">+1</button>
<button @click="updateQuantity(item.id, item.quantity - 1)">-1</button>
</div>
<p><strong>总价:¥{{ totalPrice }}</strong></p>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
// 响应式数据:购物车商品列表
const cartItems = ref([
{ id: 1, name: '苹果', price: 5, quantity: 2 },
{ id: 2, name: '香蕉', price: 3, quantity: 1 },
]);
// 计算属性:总价(基于 cartItems 动态计算)
const totalPrice = computed(() => {
return cartItems.value.reduce((sum, item) => sum + (item.price * item.quantity), 0);
});
// 监听器:监听购物车商品列表变化(如数量修改)
watch(cartItems, (newItems) => {
console.log('🛒 购物车商品列表已更新,当前商品:', newItems);
}, { deep: true }); // 深度监听(因为商品对象的 quantity 可能变化)
// 方法:更新商品数量
const updateQuantity = (id, newQuantity) => {
if (newQuantity < 0) newQuantity = 0; // 数量不能小于 0
const item = cartItems.value.find(item => item.id === id);
if (item) {
item.quantity = newQuantity;
}
};
</script>
<style scoped>
.cart {
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
margin: 10px;
}
.cart-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
padding: 8px;
background: #f9f9f9;
border-radius: 4px;
}
button {
padding: 4px 8px;
background: #007bff;
color: white;
border: none;
border-radius: 2px;
cursor: pointer;
}
</style>
-
修改商品数量(点击“+1”或“-1”按钮)时,总价( totalPrice
)实时更新; -
控制台输出监听日志,显示购物车商品列表的变化(如数量修改后的新列表)。
五、原理解释
1. 组合式 API 的核心机制
1.1 响应式数据(ref
与 reactive
)
-
ref
:用于定义 基本类型或对象引用 的响应式数据(如const count = ref(0)
)。访问或修改ref
的值时,需通过.value
属性(如count.value++
)。 -
reactive
:用于定义 对象类型的响应式数据(如const state = reactive({ user: null })
)。访问或修改reactive
的属性时,可直接通过属性名(如state.user.name = '张三'
)。
1.2 生命周期钩子(onXxx
函数)
onXxx
函数,用于替代选项式 API 的生命周期钩子:-
onBeforeMount
→ 类似beforeMount
(组件即将挂载) -
onMounted
→ 类似mounted
(组件已挂载到 DOM) -
onBeforeUpdate
→ 类似beforeUpdate
(组件即将更新) -
onUpdated
→ 类似updated
(组件已更新) -
onBeforeUnmount
→ 类似beforeDestroy
(组件即将销毁) -
onUnmounted
→ 类似destroyed
(组件已销毁)
1.3 计算属性(computed
)与监听器(watch
)
-
computed
:用于定义 基于其他响应式数据动态计算的值(如总价基于商品数量和价格)。返回一个只读的响应式引用(需通过.value
访问)。 -
watch
:用于 监听响应式数据的变化并执行副作用(如数据变化时发送请求、输出日志)。支持监听单个数据或对象,可配置深度监听(deep: true
)。
2. 逻辑聚合的核心优势
data
、methods
、生命周期
等不同选项中。例如:-
传统选项式 API:数据( data
)、方法(methods
)、生命周期(created
)分散在不同选项,逻辑关联性弱; -
组合式 API:通过 setup()
函数(或<script setup>
语法糖)将相关的数据(ref
)、方法(普通函数)、生命周期(onMounted
)组织在一起,逻辑更集中。
六、核心特性
|
|
---|---|
|
|
|
ref 、reactive 、computed 、watch 等函数定义响应式逻辑; |
|
|
|
|
|
useUser 、useCart )封装通用逻辑,在多个组件中复用; |
|
onXxx 函数精确控制组件在不同阶段的逻辑执行; |
七、原理流程图及原理解释
原理流程图(组合式 API 执行流程)
+-----------------------+
| 组件初始化 |
| (setup() 函数执行) | --> Vue 3 中组合式 API 的入口(<script setup> 隐式包含 setup)
+-----------------------+
|
v
+-----------------------+
| 定义响应式数据 | --> 通过 ref() 或 reactive() 定义(如 count = ref(0))
+-----------------------+
|
v
+-----------------------+
| 定义方法 | --> 普通函数(如 increment() { count.value++ })
+-----------------------+
|
v
+-----------------------+
| 注册生命周期钩子 | --> 通过 onMounted()、onUpdated() 等函数(如 onMounted(() => {}))
+-----------------------+
|
v
+-----------------------+
| 计算属性与监听器 | --> 通过 computed() 和 watch() 定义(如 totalPrice = computed(...))
+-----------------------+
|
v
+-----------------------+
| 组件渲染与交互 | --> 模板中通过响应式数据和方法实现动态更新
+-----------------------+
原理解释
-
组件初始化:Vue 3 在创建组件时,会优先执行 <script setup>
中的代码(或显式的setup()
函数),这是组合式 API 的入口; -
响应式数据定义:通过 ref
或reactive
定义组件的状态(如count
、user
),这些数据会被 Vue 的响应式系统追踪; -
方法定义:普通函数(如 increment
)可以直接操作响应式数据(通过.value
访问ref
的值); -
生命周期注册:通过 onMounted
、onUpdated
等函数注册生命周期逻辑,在组件对应阶段执行(如onMounted
中发送初始请求); -
计算属性与监听器: computed
用于定义依赖其他数据的动态值(如总价),watch
用于监听数据变化并执行副作用(如日志输出); -
渲染与交互:模板中通过响应式数据和方法实现动态更新,当数据变化时,Vue 自动重新渲染相关部分。
八、环境准备
1. 开发环境
-
Vue 3:组合式 API 是 Vue 3 的核心特性(Vue 2 需通过插件 @vue/composition-api
实现,但不推荐); -
开发工具:Vue CLI 或 Vite(推荐 Vite,启动更快); -
构建工具:确保项目配置支持 ES Modules 和现代 JavaScript 语法。
2. 项目配置
-
若使用 TypeScript,确保 tsconfig.json
中启用了严格的类型检查(如"strict": true
),以充分利用组合式 API 的类型推断; -
推荐使用 <script setup>
语法糖(更简洁,无需显式返回数据和方法)。
九、实际详细应用代码示例实现
完整项目代码(整合上述场景)
1. 计数器组件(Counter.vue)
2. 自定义组合函数(useUser.js)与用户详情组件(UserDetail.vue)
3. 购物车组件(Cart.vue)
4. 主应用(App.vue)
<template>
<div id="app">
<h1>Vue 3 组合式 API 基础示例</h1>
<!-- 场景 1:计数器 -->
<section>
<h2>场景 1:计数器</h2>
<Counter />
</section>
<!-- 场景 2:用户详情(使用组合函数) -->
<section>
<h2>场景 2:用户详情(逻辑复用)</h2>
<UserDetail />
</section>
<!-- 场景 3:购物车 -->
<section>
<h2>场景 3:购物车(计算属性与监听器)</h2>
<Cart />
</section>
</div>
</template>
<script setup>
import Counter from './components/Counter.vue';
import UserDetail from './components/UserDetail.vue';
import Cart from './components/Cart.vue';
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
section {
margin-bottom: 40px;
border: 1px solid #eee;
padding: 20px;
border-radius: 8px;
}
</style>
-
页面包含三个场景的组件(计数器、用户详情、购物车),验证组合式 API 的基础功能(响应式数据、生命周期、逻辑复用、计算属性)。
十、运行结果
1. 计数器的表现
-
点击“+1”或“-1”按钮时,计数实时更新; -
控制台输出组件挂载日志。
2. 用户详情的表现
-
点击“加载用户 1”或“加载用户 2”按钮,模拟异步请求后显示用户信息(ID、姓名、年龄); -
加载过程中显示“加载中...”,出错时显示错误提示。
3. 购物车的表现
-
修改商品数量时,总价实时更新; -
控制台输出购物车商品列表的变化日志。
十一、测试步骤以及详细代码
1. 测试目标
-
响应式数据( ref
)是否能正确更新并触发视图渲染; -
生命周期钩子( onMounted
)是否在组件挂载时执行; -
逻辑复用(自定义组合函数 useUser
)是否能在多个组件中复用; -
计算属性( computed
)和监听器(watch
)是否能按预期工作。
2. 测试步骤
步骤 1:启动项目
npm run serve # Vue 2(若使用 Vue 3 推荐 npm run dev 或 vite 启动)
# 或 npm run dev # Vue 3 (Vite)
http://localhost:5173
(Vite 默认端口),查看各场景组件。步骤 2:测试计数器
-
点击“+1”和“-1”按钮,观察计数是否实时更新; -
检查控制台是否有组件挂载日志。
步骤 3:测试用户详情
-
点击“加载用户 1”和“加载用户 2”按钮,观察用户信息是否正确显示; -
检查加载状态和错误提示是否按预期出现。
步骤 4:测试购物车
-
修改商品数量(点击“+1”或“-1”),观察总价是否实时计算; -
检查控制台是否有购物车商品列表变化的日志输出。
十二、部署场景
1. 生产环境注意事项
-
响应式数据优化:避免在组合函数中定义过多全局响应式数据(可能导致内存占用过高); -
生命周期管理:在 onUnmounted
中清理定时器、取消网络请求、移除事件监听器,避免内存泄漏; -
TypeScript 类型安全:为组合函数定义明确的类型(如泛型 ref<T>
),提升代码健壮性; -
逻辑复用规范:将常用的组合函数(如 useUser
、useCart
)放在src/composables/
目录下,统一管理。
2. 适用场景
-
复杂业务逻辑:如用户认证、数据分页、表单验证; -
跨组件复用:如工具函数(本地存储、窗口大小监听)、API 请求封装; -
**大型组件拆分
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)