composables

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Composables

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.
Composables是使用Vue的Composition API封装并复用有状态逻辑的函数。它们以简洁、可组合的函数替代选项式API(Options API)碎片化的代码组织方式,可在多个组件间共享。

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
    use
    prefix by convention (e.g.,
    useCounter
    ,
    useWidth
    )
  • Use
    ref()
    ,
    reactive()
    , and lifecycle hooks (
    onMounted
    ,
    onBeforeUnmount
    ) inside composables
  • 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
App
component that renders two individual child components —
Count
and
Width
.
html
<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
App
.
The
<template>
section defines the markup of the component. In this case, it contains a
<div>
element with the class "App" that wraps two child components:
<Count>
and
<Width>
. These child components are passed certain properties using Vue's attribute binding syntax (
:count
,
:increment
,
:decrement
, and
:width
).
The
<script>
section contains the JavaScript code for the component. Within the component definition, we have:
  • The
    data
    method which returns an object containing the initial data properties of the component, which are
    count
    and
    width
    initialized to 0.
  • The
    mounted()
    lifecycle hook is used to execute code after the component has been mounted in the DOM. In this case, it calls the
    handleResize()
    method and adds an event listener for the resize event.
  • The
    beforeUnmount()
    lifecycle hook is used to execute code before the component is unmounted and destroyed. Here, it removes the event listener for the resize event.
  • The
    methods
    object contains the component's methods. It defines
    increment()
    ,
    decrement()
    , and
    handleResize()
    methods that manipulate the count and width data properties based on certain events or actions.
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
<Count>
component. Similarly, the width is automatically updated whenever the window is resized.
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中仍然适用,但随着组件变得更大、更复杂,其管理和维护会变得困难。在特定选项内定义组件逻辑会降低代码的可读性和理解难度,尤其是处理大型组件时。在这种架构下,提取和复用组件间的通用逻辑也较为困难。
接下来看一个简单示例:
App
组件渲染两个子组件——
Count
Width
html
<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
    方法返回一个对象,包含组件的初始数据属性
    count
    width
    ,初始值均为0。
  • mounted()
    生命周期钩子用于在组件挂载到DOM后执行代码,这里调用了
    handleResize()
    方法并添加了resize事件监听器。
  • beforeUnmount()
    生命周期钩子用于在组件卸载和销毁前执行代码,这里移除了resize事件监听器。
  • methods
    对象包含组件的方法,定义了
    increment()
    decrement()
    handleResize()
    方法,用于根据事件或操作修改count和width数据属性。
运行应用时,会实时显示当前计数和窗口的内部宽度。用户可以通过
<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
setup()
option which serves as the entry point for utilizing the Composition API.
html
<!-- Template -->

<script>
  export default {
    name: "MyComponent",
    setup() {
      // the setup function
    },
  };
</script>

<!-- Styles -->
The
setup()
function is executed before a component is created and when the props of the component are available.
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
<template>
of our component remains the same but in the
<script>
section of our component, we now utilize the Composition API with the
setup()
function.
Inside the
setup()
function, we:
  • Define the
    count
    and
    width
    reactive variables using the
    ref()
    function — the function that accepts a single primitive value (e.g. string, number, etc.) and returns a reactive/mutable object.
  • We also define the custom functions
    increment()
    ,
    decrement()
    , and
    handleResize()
    . These functions are similar to the methods we defined in our previous Options API example.
  • We use the
    onMounted()
    lifecycle function to call the custom
    handleResize()
    function and add an event listener for the resize event when the component is mounted. Similarly, we use the
    onBeforeUnmount()
    lifecycle function to remove the event listener for the resize event before the component is unmounted.
  • The reactive variables and functions defined in the
    setup()
    function are then returned, making them accessible in the component template.
Composition API可以看作是提供Vue核心能力的独立函数集合。这些函数主要用于单个
setup()
选项中,该选项是使用Composition API的入口点。
html
<!-- Template -->

<script>
  export default {
    name: "MyComponent",
    setup() {
      // the setup function
    },
  };
</script>

<!-- Styles -->
setup()
函数在组件创建前、组件props可用时执行。
借助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>
组件的
<template>
部分保持不变,但
<script>
部分现在使用Composition API的
setup()
函数。
setup()
函数内部:
  • 使用
    ref()
    函数定义
    count
    width
    响应式变量——该函数接收单个原始值(如字符串、数字等)并返回一个响应式/可变对象。
  • 定义了自定义函数
    increment()
    decrement()
    handleResize()
    ,这些函数与之前选项式API示例中的方法类似。
  • 使用
    onMounted()
    生命周期函数在组件挂载时调用自定义的
    handleResize()
    函数并添加resize事件监听器。同样,使用
    onBeforeUnmount()
    生命周期函数在组件卸载前移除resize事件监听器。
  • 返回
    setup()
    函数中定义的响应式变量和函数,使其可在组件模板中访问。

Composables

Composables

With our previous code example, one might still wonder how the
setup()
function offers any advantage to development since it appears that it just requires us to declare component options within a single function.
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
useCounter
, a composable that encapsulates the counter functionality and returns the current value of
count
, an
increment()
method, and a
decrement()
method.
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
useWidth()
that encapsulates the width functionality of our app.
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
App
component, we can now use the composable functions to achieve the same outcome:
html
<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的全局组合函数,并且可以在多个组件中轻松使用这些函数来实现相同的效果。
让我们进一步扩展之前的计数器和宽度示例,创建封装共享逻辑的组合式函数,以便在组件间复用。
首先,创建一个名为
useCounter
的组合式函数,它封装了计数器功能,返回当前
count
值、
increment()
方法和
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,
  };
}
现在,我们可以在
App
组件中使用这些组合式函数来实现相同的效果:
html
<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应用变得更加容易!

Source

来源

References

参考资料