好的,我们来深入探讨一下 Vue 3 的 Composable。
1. 什么是 Composable?
Composable 是 Vue 3 中一个核心的概念和功能,它是可复用的、有状态的逻辑块,用于封装和提取组件中的重复逻辑,让代码更清晰、更易于维护。
你可以把它理解为:专门用来存放 "可以被多个组件复用的业务逻辑" 的函数。
2. 为什么需要 Composable?
在 Vue 2 中,我们通常使用 Mixins 来复用逻辑。但 Mixins 存在一些问题:
- 命名冲突:多个 Mixin 可能会定义相同名称的数据或方法,导致覆盖。
- 来源不清晰:当一个组件使用了多个 Mixin 后,很难追踪某个数据或方法到底来自哪个 Mixin。
- 逻辑耦合度高:Mixins 和组件之间是隐式依赖关系,不易于单独测试和维护。
而 Composable 则完美解决了这些问题:
- 明确的依赖关系:通过函数调用和返回值来显式地传入和获取数据,来源清晰。
- 无命名冲突:返回的内容通过解构赋值的方式被组件接收,组件可以自己决定命名。
- 更好的类型推断:对于 TypeScript 非常友好,能提供完整的类型提示。
- 更易于测试:Composable 本身就是一个函数,可以独立进行单元测试。
3. 如何创建一个 Composable?
创建一个 Composable 非常简单,它就是一个以 use 开头的函数 (这是一个约定,便于识别),在函数内部可以使用 Vue 的响应式 API(如 ref, reactive, computed, watch 等),并返回需要暴露给组件使用的状态和方法。
示例:创建一个处理计数器逻辑的 Composable
运行
javascript
// src/composables/useCounter.js
import { ref, computed, onMounted } from 'vue';
// 定义一个 Composable 函数,通常以 use 开头
export function useCounter(initialValue = 0) {
// 1. 定义响应式状态
const count = ref(initialValue);
// 2. 定义基于状态的计算属性
const doubleCount = computed(() => count.value * 2);
// 3. 定义修改状态的方法
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const reset = () => {
count.value = initialValue;
};
// 4. 可以使用生命周期钩子
onMounted(() => {
console.log(`计数器已初始化,初始值为: ${count.value}`);
});
// 5. 返回需要暴露给组件的内容
return {
count,
doubleCount,
increment,
decrement,
reset
};
}
4. 如何在组件中使用 Composable?
在组件中,你只需要 导入并调用 这个 Composable 函数,然后通过解构赋值的方式获取其返回的状态和方法即可。
示例:在组件中使用 useCounter
vue
<!-- src/components/CounterDisplay.vue -->
<template>
<div>
<p>当前计数: {{ count }}</p>
<p>计数的两倍: {{ doubleCount }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from '@/composables/useCounter';
// 调用 Composable 函数
// 可以传入初始值,如 useCounter(10)
// 通过解构赋值获取返回的状态和方法
const { count, doubleCount, increment, decrement, reset } = useCounter();
</script>
关键点:
- 每次调用
useCounter(),都会创建一个全新的、独立的 状态实例。这意味着如果两个组件都使用了useCounter,它们的状态是完全隔离的,互不影响。 - 组件可以根据需要,只解构自己需要的部分。
5. Composable 的最佳实践
-
命名规范:
- Composable 函数名必须以
use开头 ,例如useCounter,useUser,useCart。 - 文件名通常与函数名一致,例如
useCounter.js。
- Composable 函数名必须以
-
单一职责原则:
- 一个 Composable 应该只负责一个特定的功能或逻辑。如果一个 Composable 变得过于庞大和复杂,应该考虑将其拆分成多个更小的、职责更单一的 Composable。
-
组合与嵌套:
-
Composable 可以相互调用。这是一个非常强大的特性,允许你构建复杂的逻辑。
-
例如,一个
useCartComposable 内部可以调用useUserComposable 来获取当前用户信息,以便计算购物车商品的会员价格。
运行
javascript// src/composables/useCart.js import { useUser } from './useUser'; export function useCart() { const { user } = useUser(); // 调用另一个 Composable // ... 购物车相关逻辑,可以使用 user 的信息 const cartItems = ref([]); const calculateDiscountPrice = (item) => { if (user.value?.isVIP) { return item.price * 0.9; // VIP 9 折 } return item.price; }; // ... return { cartItems, calculateDiscountPrice }; } -
-
避免在 Composable 中访问组件实例:
- 理想情况下,Composable 应该是纯逻辑 的封装,不应该直接依赖于 Vue 组件实例 (
this)。 - 如果确实需要使用组件实例的属性(如
$route,$store),应该通过参数的形式从组件中传递进来,或者使用 Vue 提供的getCurrentInstance()函数(但这会降低 Composable 的通用性,应谨慎使用)。
- 理想情况下,Composable 应该是纯逻辑 的封装,不应该直接依赖于 Vue 组件实例 (
总结
Composable 是 Vue 3 中替代 Mixins 的更优、更现代的逻辑复用方案。
- 它通过函数 的形式封装逻辑,通过返回值暴露状态和方法。
- 它实现了逻辑的复用和隔离,让组件代码更简洁、更清晰。
- 它遵循明确的依赖关系 和单一职责原则,极大地提升了代码的可维护性和可测试性。
- 在 Vue 3 项目中,任何可复用的逻辑都应该被提取为 Composable。