composables
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseComposables
Composables
Table of Contents
目录
Composables are functions that encapsulate and reuse stateful logic using Vue's Composition API. They replace the Options API's fragmented code organization with clean, composable functions that can be shared across components.
When to Use
适用场景
- Use this when you need to share stateful logic across multiple components without duplication
- This is helpful for extracting concerns like data fetching, event listeners, or timers into reusable functions
- 当你需要在多个组件间共享有状态逻辑且避免代码重复时使用
- 有助于将数据获取、事件监听或定时器等逻辑提取为可复用函数
Instructions
使用指南
- Name composable functions with the prefix by convention (e.g.,
use,useCounter)useWidth - Use ,
ref(), and lifecycle hooks (reactive(),onMounted) inside composablesonBeforeUnmount - Return reactive state and methods from composables for use in component templates
- Prefer composables over the Options API for better code organization, reusability, and TypeScript support
- 按照惯例,组合式函数名称以前缀开头(例如:
use、useCounter)useWidth - 在Composables内部使用、
ref()以及生命周期钩子(reactive()、onMounted)onBeforeUnmount - 从Composables中返回响应式状态和方法,供组件模板使用
- 相比选项式API,优先使用Composables以获得更优的代码组织、复用性和TypeScript支持
Details
详细说明
Options API
选项式API(Options API)
Before the introduction of the Composition API in Vue, developers relied on the Options API to organize component logic which include reactive data, lifecycle methods, computed properties, and more. The Options API allowed defining these aspects within specific options, as shown in the example below:
html
<!-- Template -->
<script>
export default {
name: "MyComponent",
props: {
// props
},
data() {
// data
},
computed: {
// computed properties
},
watch: {
// properties to watch
},
methods: {
// methods
},
created() {
// lifecyle methods like created
},
// ...
};
</script>
<!-- Styles -->While this approach served its purpose and is still applicable in Vue v3, it can become challenging to manage and maintain as components grow larger and more complex. Defining component logic within specific options can make it harder to read and understand the code, especially when dealing with extensive components. Extracting and reusing common logic between components can also be difficult in this setup.
Let's take a look at a simple example of an component that renders two individual child components — and .
AppCountWidthhtml
<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>The code snippet above represents a Vue single-file component (SFC) named .
AppThe section defines the markup of the component. In this case, it contains a element with the class "App" that wraps two child components: and . These child components are passed certain properties using Vue's attribute binding syntax (, , , and ).
<template><div><Count><Width>:count:increment:decrement:widthThe section contains the JavaScript code for the component. Within the component definition, we have:
<script>- The method which returns an object containing the initial data properties of the component, which are
dataandcountinitialized to 0.width - The lifecycle hook is used to execute code after the component has been mounted in the DOM. In this case, it calls the
mounted()method and adds an event listener for the resize event.handleResize() - The lifecycle hook is used to execute code before the component is unmounted and destroyed. Here, it removes the event listener for the resize event.
beforeUnmount() - The object contains the component's methods. It defines
methods,increment(), anddecrement()methods that manipulate the count and width data properties based on certain events or actions.handleResize()
When the app is run, the current count and the window's inner width are displayed in real-time. The user can interact with the component by incrementing and decrementing the count using the buttons in the component. Similarly, the width is automatically updated whenever the window is resized.
<Count>Even though this component is small in size, the logic inside it is already intertwined. Some parts are dedicated to the functionality of the counter, while others pertain to the width logic. As the component grows, organizing and locating related logic within the component would become more challenging.
To address these challenges, the Vue team introduced the Composition API in Vue v3.
在Vue引入Composition API之前,开发者依赖选项式API来组织组件逻辑,包括响应式数据、生命周期方法、计算属性等。选项式API允许在特定选项内定义这些内容,如下例所示:
html
<!-- Template -->
<script>
export default {
name: "MyComponent",
props: {
// props
},
data() {
// data
},
computed: {
// computed properties
},
watch: {
// properties to watch
},
methods: {
// methods
},
created() {
// lifecyle methods like created
},
// ...
};
</script>
<!-- Styles -->虽然这种方法曾经发挥作用,且在Vue 3中仍然适用,但随着组件变得更大、更复杂,其管理和维护会变得困难。在特定选项内定义组件逻辑会降低代码的可读性和理解难度,尤其是处理大型组件时。在这种架构下,提取和复用组件间的通用逻辑也较为困难。
接下来看一个简单示例:组件渲染两个子组件——和。
AppCountWidthhtml
<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>这段代码是一个名为的Vue单文件组件(SFC)。
App<template><div><Count><Width>:count:increment:decrement:width<script>- 方法返回一个对象,包含组件的初始数据属性
data和count,初始值均为0。width - 生命周期钩子用于在组件挂载到DOM后执行代码,这里调用了
mounted()方法并添加了resize事件监听器。handleResize() - 生命周期钩子用于在组件卸载和销毁前执行代码,这里移除了resize事件监听器。
beforeUnmount() - 对象包含组件的方法,定义了
methods、increment()和decrement()方法,用于根据事件或操作修改count和width数据属性。handleResize()
运行应用时,会实时显示当前计数和窗口的内部宽度。用户可以通过组件中的按钮增减计数,窗口调整大小时宽度会自动更新。
<Count>尽管这个组件规模很小,但内部逻辑已经相互交织。一部分用于计数器功能,另一部分与宽度逻辑相关。随着组件规模扩大,在组件内组织和定位相关逻辑会变得更具挑战性。
为解决这些问题,Vue团队在Vue 3中引入了Composition API。
Composition API
Composition API
The Composition API can be seen as an API that provides standalone functions representing Vue's core capabilities. These functions are primarily used within a single option which serves as the entry point for utilizing the Composition API.
setup()html
<!-- Template -->
<script>
export default {
name: "MyComponent",
setup() {
// the setup function
},
};
</script>
<!-- Styles -->The function is executed before a component is created and when the props of the component are available.
setup()With the Composition API, we can import standalone functions to help us access Vue's core capabilities within our component. Let's rewrite the counter and width example we've seen above while relying on the Composition API syntax.
html
<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>The of our component remains the same but in the section of our component, we now utilize the Composition API with the function.
<template><script>setup()Inside the function, we:
setup()- Define the and
countreactive variables using thewidthfunction — the function that accepts a single primitive value (e.g. string, number, etc.) and returns a reactive/mutable object.ref() - We also define the custom functions ,
increment(), anddecrement(). These functions are similar to the methods we defined in our previous Options API example.handleResize() - We use the lifecycle function to call the custom
onMounted()function and add an event listener for the resize event when the component is mounted. Similarly, we use thehandleResize()lifecycle function to remove the event listener for the resize event before the component is unmounted.onBeforeUnmount() - The reactive variables and functions defined in the function are then returned, making them accessible in the component template.
setup()
Composition API可以看作是提供Vue核心能力的独立函数集合。这些函数主要用于单个选项中,该选项是使用Composition API的入口点。
setup()html
<!-- Template -->
<script>
export default {
name: "MyComponent",
setup() {
// the setup function
},
};
</script>
<!-- Styles -->setup()借助Composition API,我们可以导入独立函数来在组件中使用Vue的核心能力。让我们使用Composition API语法重写上面的计数器和宽度示例。
html
<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>组件的部分保持不变,但部分现在使用Composition API的函数。
<template><script>setup()在函数内部:
setup()- 使用函数定义
ref()和count响应式变量——该函数接收单个原始值(如字符串、数字等)并返回一个响应式/可变对象。width - 定义了自定义函数、
increment()和decrement(),这些函数与之前选项式API示例中的方法类似。handleResize() - 使用生命周期函数在组件挂载时调用自定义的
onMounted()函数并添加resize事件监听器。同样,使用handleResize()生命周期函数在组件卸载前移除resize事件监听器。onBeforeUnmount() - 返回函数中定义的响应式变量和函数,使其可在组件模板中访问。
setup()
Composables
Composables
With our previous code example, one might still wonder how the function offers any advantage to development since it appears that it just requires us to declare component options within a single function.
setup()One of the fantastic benefits of adopting the composition API is the capability to extract and reuse shared logic between components. This is driven by the fact that we can simply declare functions of our own that use Vue's globally available composition functions and have our functions be easily used in multiple components to achieve the same outcome.
Let's take our previous counter and width example further by creating composable functions that encapsulates shared logic that can be reused across components.
First, let's create a composable function called , a composable that encapsulates the counter functionality and returns the current value of , an method, and a method.
useCountercountincrement()decrement()By convention, composable function names start with the "use" keyword.
js
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,
};
}Similarly, we can create a composable called that encapsulates the width functionality of our app.
useWidth()js
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,
};
}In our component, we can now use the composable functions to achieve the same outcome:
Apphtml
<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>With these changes, our app will function the same as it did before but in a more composable setting.
By using composable functions in the Composition API setting, we were able to break the context of our app down into smaller, reusable pieces that separated the logic.
Using composable functions in Vue made it easier to separate the logic of our component into several smaller pieces. Reusing the same stateful logic now becomes easy since we are no longer confined to organizing our code within specific options in the Options API.
With composable functions, we have the flexibility to extract and reuse shared logic across components. This separation of concerns allows us to focus on specific functionality within each composable function making our code more modular and maintainable.
By breaking down the logic into smaller, reusable pieces, we can compose our components using these composable functions, bringing together the necessary functionality without duplicating code. This approach promotes code reusability and reduces the risk of code duplication and inconsistencies.
Additionally, using the Composition API provides better readability and understandability of the component's logic. Each composable function encapsulates a specific aspect of the component's behavior, making it easier to reason about and test. It also allows for easier collaboration among team members, as the code becomes more structured and organized.
Lastly, building Vue apps with the Composition API allows for better type inference. Since the Composition API helps us handle our component logic with variables and standard JavaScript functions, it becomes a lot easier to build large-scale Vue applications with a static type system like TypeScript!
对于之前的代码示例,有人可能会疑惑函数对开发有什么优势,因为看起来只是将组件选项声明在单个函数中。
setup()采用组合式API的一大优势是能够提取和复用组件间的共享逻辑。这是因为我们可以轻松声明自己的函数,这些函数使用Vue的全局组合函数,并且可以在多个组件中轻松使用这些函数来实现相同的效果。
让我们进一步扩展之前的计数器和宽度示例,创建封装共享逻辑的组合式函数,以便在组件间复用。
首先,创建一个名为的组合式函数,它封装了计数器功能,返回当前值、方法和方法。
useCountercountincrement()decrement()按照惯例,组合式函数名称以"use"关键字开头。
js
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()js
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,
};
}现在,我们可以在组件中使用这些组合式函数来实现相同的效果:
Apphtml
<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>经过这些修改,应用的功能与之前相同,但架构更具组合性。
通过在Composition API环境中使用组合式函数,我们将应用的上下文拆分为更小的、可复用的模块,实现了逻辑分离。
在Vue中使用组合式函数可以更轻松地将组件逻辑拆分为多个小模块。现在复用相同的有状态逻辑变得简单,因为我们不再受限于选项式API中特定选项的代码组织方式。
借助组合式函数,我们可以灵活地提取和复用组件间的共享逻辑。这种关注点分离让我们能够专注于每个组合式函数的特定功能,使代码更具模块化和可维护性。
通过将逻辑拆分为更小的可复用模块,我们可以使用这些组合式函数构建组件,整合所需功能而无需重复代码。这种方法提升了代码复用性,减少了代码重复和不一致的风险。
此外,使用Composition API提升了组件逻辑的可读性和可理解性。每个组合式函数封装了组件行为的特定方面,使其更易于推理和测试。这也有助于团队成员间的协作,因为代码结构更清晰、组织更有序。
最后,使用Composition API构建Vue应用可以实现更好的类型推断。由于Composition API帮助我们通过变量和标准JavaScript函数处理组件逻辑,使用TypeScript等静态类型系统构建大型Vue应用变得更加容易!