关于 composables
面向 Vue 开发者的组合式函数指南,讲解如何借助 Composition API 封装并复用有状态逻辑,适用于将数据获取、事件处理和计时器等职责从组件中分离。
主要功能
- 01复用有状态函数
- 02Composition API 模式
- 03响应式状态与生命周期钩子
- 04向组件返回状态和方法
- 05类型推断支持
适用场景
- 01在 Vue 组件间共享逻辑
- 02提取数据获取行为
- 03管理事件监听器或计时器
- 04整理复杂的组件逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ patternsdev/ skills --skill composables安装后在 Claude Code 中输入/composables确认已加载。
详细介绍
组合式函数
目录
组合式函数是使用 Vue 的组合式 API 封装和复用有状态逻辑的函数。它们用简洁、可组合且可在组件间共享的函数,取代了选项式 API 中零散的代码组织方式。
何时使用
- 需要在多个组件间共享有状态逻辑且不想重复编写时,使用组合式函数
- 将数据获取、事件监听器或计时器等关注点提取为可复用函数时,组合式函数会很有帮助
使用说明
- 按照惯例,组合式函数以
use为前缀命名(例如useCounter、useWidth) - 在组合式函数中使用
ref()、reactive()和生命周期钩子(onMounted、onBeforeUnmount) - 从组合式函数中返回响应式状态和方法,以便在组件模板中使用
- 相比选项式 API,优先使用组合式函数,以获得更好的代码组织、可复用性和 TypeScript 支持
详情
选项式 API
在 Vue 引入组合式 API 之前,开发者依赖选项式 API来组织组件逻辑,其中包括响应式数据、生命周期方法、计算属性等。选项式 API 允许在特定选项中定义这些内容,如下例所示:
<script>
export default {
name: "MyComponent",
props: {
// props
},
data() {
// data
},
computed: {
// computed properties
},
watch: {
// properties to watch
},
methods: {
// methods
},
created() {
// lifecyle methods like created
},
// ...
};
</script>
这种方式满足了当时的需求,在 Vue v3 中仍然适用,但随着组件变得更大、更复杂,管理和维护起来可能会很困难。在特定选项中定义组件逻辑会让代码更难阅读和理解,尤其是在处理大型组件时。在这种结构中,提取并复用组件间的通用逻辑也可能很困难。
我们来看一个简单示例:App 组件渲染了两个独立的子组件——Count 和 Width。
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
export default {
name: "App",
data() {
return {
count: 0,
width: 0,
};
},
mounted() {
this.handleResize();
window.addEventListener("resize", this.handleResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleResize);
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
handleResize() {
this.width = window.innerWidth;
},
},
components: {
Count,
Width,
},
};
</script>
上面的代码片段是一个名为 App 的 Vue 单文件组件(SFC)。
<template> 部分定义了组件的标记。在本例中,它包含一个类名为 "App" 的 <div> 元素,其中包裹着两个子组件:<Count> 和 <Width>。这些子组件通过 Vue 的属性绑定语法(:count、:increment、:decrement 和 :width)传入了一些属性。
<script> 部分包含组件的 JavaScript 代码。在组件定义中,包括:
data方法:返回一个包含组件初始数据属性的对象,即初始化为 0 的count和width。mounted()生命周期钩子:用于在组件挂载到 DOM 后执行代码。在本例中,它会调用handleResize()方法,并为resize事件添加事件监听器。beforeUnmount()生命周期钩子:用于在组件卸载并销毁之前执行代码。在这里,它会移除resize事件的事件监听器。methods对象:包含组件的方法。它定义了increment()、decrement()和handleResize()方法,这些方法会根据特定事件或操作来修改count和width数据属性。
运行应用后,当前计数和窗口内部宽度会实时显示。用户可以通过 <Count> 组件中的按钮递增或递减计数,与组件进行交互。同样,每当窗口大小发生变化时,宽度也会自动更新。
尽管这个组件规模不大,其中的逻辑却已经相互交织。一部分逻辑用于实现计数器功能,另一部分则与宽度相关。随着组件不断扩大,在组件中组织和定位相关逻辑会变得更加困难。
为了解决这些问题,Vue 团队在 Vue v3 中引入了组合式 API。
组合式 API
组合式 API 可以看作是一组表示 Vue 核心功能的独立函数所组成的 API。这些函数主要在单个 setup() 选项中使用,该选项是使用组合式 API 的入口。
<script>
export default {
name: "MyComponent",
setup() {
// the setup function
},
};
</script>
setup() 函数会在组件创建之前执行,此时组件的 props 已可用。
使用组合式 API,我们可以导入独立函数,以便在组件中使用 Vue 的核心功能。下面我们使用组合式 API 语法,重写之前的计数器和宽度示例。
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from "vue";
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
export default {
name: "App",
setup() {
const count = ref(0);
const width = ref(0);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const handleResize = () => {
width.value = window.innerWidth;
};
onMounted(() => {
handleResize();
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
});
return {
count,
width,
increment,
decrement,
};
},
components: {
Count,
Width,
},
};
</script>
组件的 <template> 保持不变,但在组件的 <script> 部分,我们现在使用组合式 API 的 setup() 函数。
在 setup() 函数中,我们:
- 使用
ref()函数定义响应式变量count和width。该函数接受一个原始值(例如字符串、数字等),并返回一个响应式/可变对象。 - 还定义了自定义函数
increment()、decrement()和handleResize()。这些函数与我们在之前的选项式 API 示例中定义的方法类似。 - 使用
onMounted()生命周期函数调用自定义函数handleResize(),并在组件挂载时为 resize 事件添加事件监听器。同样,我们使用onBeforeUnmount()生命周期函数,在组件卸载前移除 resize 事件的事件监听器。 - 然后返回在
setup()函数中定义的响应式变量和函数,使它们可以在组件模板中访问。
组合式函数
通过前面的代码示例,你可能仍会疑惑:setup() 函数有什么开发优势?因为看起来它只是要求我们在一个函数中声明组件选项。
采用组合式 API 的一大优势是,能够提取并复用组件之间共享的逻辑。这是因为我们可以直接声明自己的函数,在其中使用 Vue 全局可用的组合式函数,并让这些函数_在多个组件中轻松复用,以实现相同的效果_。
让我们进一步完善前面的计数器和宽度示例,创建可在多个组件中复用的组合式函数来封装共享逻辑。
首先,创建一个名为 useCounter 的组合式函数,用于封装计数器功能,并返回 count 的当前值、increment() 方法和 decrement() 方法。
按照惯例,组合式函数的名称以“use”开头。
import { ref } from "vue";
export function useCounter(initialCount = 0) {
const count = ref(initialCount);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return {
count,
increment,
decrement,
};
}
同样,我们可以创建一个名为 useWidth() 的组合式函数,用于封装应用的宽度功能。
import { ref, onMounted, onBeforeUnmount } from "vue";
export function useWidth() {
const width = ref(0);
function handleResize() {
width.value = window.innerWidth;
}
onMounted(() => {
handleResize();
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
});
return {
width,
};
}
现在,我们可以在 App 组件中使用这些组合式函数来实现相同的效果:
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
import { useCounter } from "./composables/useCounter";
import { useWidth } from "./composables/useWidth";
export default {
name: "App",
components: {
Count,
Width,
},
setup() {
const { count, increment, decrement } = useCounter(0);
const { width } = useWidth();
return {
count,
increment,
decrement,
width,
};
},
};
</script>
经过这些修改后,应用的功能与之前相同,但结构更具组合性。
在组合式 API 中使用组合式函数,我们能够将应用的上下文拆分成更小、可复用的部分,从而分离逻辑。
在 Vue 中使用组合式函数,让我们更容易将组件逻辑拆分成多个更小的部分。现在,复用相同的有状态逻辑也变得轻而易举,因为我们不再受限于在选项式 API 的特定选项中组织代码。
组合式函数让我们可以灵活地提取并复用组件之间共享的逻辑。职责分离让我们能够专注于每个组合式函数中的特定功能,使代码更加模块化且易于维护。
将逻辑拆分成更小、可复用的部分后,我们可以通过组合这些组合式函数来构建组件,汇集所需功能,同时避免代码重复。这种方法有助于提高代码复用性,并降低代码重复和不一致的风险。
此外,使用组合式 API 可以提高组件逻辑的可读性和易理解性。每个组合式函数都会封装组件行为的一个特定方面,因此更容易分析和测试。它也便于团队成员协作,因为代码结构更加清晰、有条理。
最后,使用组合式 API 构建 Vue 应用还能带来更好的类型推断。由于组合式 API 帮助我们通过变量和标准 JavaScript 函数来处理组件逻辑,因此使用 TypeScript 这样的静态类型系统构建大型 Vue 应用会容易得多!