组合式函数 | Vue.js 组合式 API 常见问答 | Vue.js

介绍

在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。

Composables 函数就是利用 Vue3 提出的 Composition API ,封装出的一些可以复用的组合式函数,目前 Vue2 也可以使用(2.7 版本可以直接使用,2.6 需要引入另外composition-api的插件)。还没写过这种风格代码的同学,可以先看文章开头引的官方文档的两篇文章,介绍的比较详细。下面我简单介绍一下:
组合式 API 的灵感来自Hooks,也是目前流行的函数式编程的思想,便于逻辑复用、测试以及类型推导,编写Vue业务代码的时候会更加的灵活。但组合式 API 不等于函数式编程,组合式 API 的特点是基于Vue数据响应系统的,而函数式编程通常强调数据不可变的。
组合式API 包括:

  • 响应式相关(ref、reactive、computed、watch 等)
  • 生命周期钩子(onMounted 和 onUnmounted 等)
  • provide、inject 等

一个简单的组合式函数示例和使用:

  1. import { computed } from 'vue'
  2. export function useAdd(a,b) {
  3. return computed(() => a.value + b.value)
  4. }
  1. import { ref } from 'vue'
  2. import { useAdd } from './add.js
  3. const a = ref(1)
  4. const b = ref(2)
  5. const c = useAdd(a,b)

最佳实践

像写工具函数一样,组合式函数也要设计好函数的接口,设计函数入参的类型以及默认值和返回值是什么样,以及后期的可扩展性。

1. options对象化

编写组合式函数时如果传入一些配置参数,可以把配置参数设计为 options 对象,好处就是不需要记住参数的位置,以及方便后期的扩展,可通过 TS 类型提示。如 Lodash 的防抖节流函数,配置项也是放在 options 对象里的。
函数内部实现通过解构获取 options 的配置项,可以赋值默认值

  1. export function useTitle(newTitle = null, options = {}) {
  2. const {
  3. document = defaultDocument,
  4. observe = false,
  5. titleTemplate = '%s',
  6. } = options
  7. // ...
  8. }

2. 动态返回

通过 options 的配置项,可以动态返回不同的值

  1. export default useNow(options) {
  2. const { controls = false } = options;
  3. // ...
  4. if (!controls) {
  5. return value;
  6. } else {
  7. return { value, controlAction1, controlAction2 };
  8. }
  9. }

如:useNow https://vueuse.org/core/useNow/

  1. import { useNow } from '@vueuse/core'
  2. const now = useNow()
  3. // 返回控制暂停/恢复的函数
  4. const { now, pause, resume } = useNow({ controls: true })

3. 灵活地使用参数

入参在使用的过程中可能是普通类型或 ref 类型:

  1. type MaybeRef<T> = Ref<T> | T

如果函数内部期待入参是普通类型,但是使用者可能传入响应式的 Ref 类型,可以使用 unref 函数获取值,兼容 ref类型:

  1. import { unref } from 'vue'
  2. function useFeature(maybeRef) {
  3. const value = unref(maybeRef)
  4. //...
  5. }

unref的实现:

  1. function unref <T> (r:Ref<T>|T): T) {
  2. return isRef(r) ? r.value : r
  3. }

如果函数内部入参是 ref 类型的,可以再使用 ref 函数包裹,该函数如果传入一个ref类型的参数会原样返回。

  1. import { ref, watch } from 'vue'
  2. export function useTitle(newTitle) {
  3. const title = ref(newTitle || document.title)
  4. watch(title, (t) => {
  5. if (t != null) document.title = t},
  6. { immediate: true }
  7. )
  8. return title
  9. }

4. 异步变同步

通过响应式数据的连接,不必使用await,在等待异步请求返回后将数据进行更新。如:

  1. const { res } = useFetch('https://xxx.com/').json()
  2. const data = computed(() => res.value?.data)
  1. import { shallowRef } from 'vue'
  2. export function useFetch<>(url) {
  3. const data = shallowRef(null)
  4. const error = shallowRef(null)
  5. fetch(unref(url)).then(r => r.json())
  6. .then(r => data.value = r)
  7. .catch(e => error.value = e)
  8. return {
  9. data,
  10. error
  11. }
  12. }

5. 简单的状态管理

由于响应性系统与组件层是解耦的,简单的场景下我们可以使用 reactive 创建一个响应式对象,在多个组件实例间共享数据。

  1. // store.js
  2. import { reactive } from 'vue'
  3. export const store = reactive({
  4. a: 0,
  5. b: 1
  6. })

复杂场景请使用 pinia 进行状态管理

参考