如何处理composition api的动态参数

July 17, 2022

之前笔者写过一篇文章composition api 的参数传递 讲到过在 vue composition api 中应该如何处理动态的参数,建议读者在阅读本文前,先阅读之前的那篇文章。

起因在看到 vueuse 的issue, 笔者认领了一个 useAverage 的需求,然后开始编码。这个函数的功能很简单,就是根据参数算一个平均值。最开始的代码大概如下

function useAverage(array: MaybeRef<number>[]) {
  const arr = unref(array)
  return arr.reduce((sum, cur) => sum + cur, 0) / arr.length
}

这个看起来没什么问题,但是,我们少考虑了一些场景,比如

const num = ref(5)
const arr = ref([1, num, 10])

// or
const arr = [1, num, 5]

const avg = useAverage(arr)

可以看到,此时,数组中某一个元素是一个 ref,这种情况,之前的实现是不能够处理的。于是,稍微修改一下

function useAverage(array: MaybeRef<MaybeRef<number>[]>) {
  return computed(() => {
    const arr = unref(array)
    return arr.reduce((sum, cur) => sum + unref(cur), 0) / arr.length
  })
}

到这里,或许就可以了,但是vueuse关于参数的响应式做得更加精确一些,他提供了一个 MaybeComputedRef

/**
 * Maybe it's a ref, or a plain value
 *
 * ```ts
 * type MaybeRef<T> = T | Ref<T>
 * ```
 */
export type MaybeRef<T> = T | Ref<T>

/**
 * Maybe it's a ref, or a plain value, or a getter function
 *
 * ```ts
 * type MaybeComputedRef<T> = T | Ref<T> | (() => T)
 * ```
 */
export type MaybeComputedRef<T> = T extends () => void
  ? never
  : (() => T) | MaybeRef<T>

可以看到 MaybeComputedRef 除了 MaybeRef 还包含了一个 getter 函数。对应的从 MaybeComputedRef获取最终值也有一个方法resolveUnref

/**
 * Normalize value/ref/getter to `ref` or `computed`.
 */
export function resolveUnref<T>(r: MaybeComputedRef<T>): T {
  return typeof r === 'function'
    ? (r as any)()
    : unref(r)
}

基于此,我们使用 MaybeComputedRef替换掉 MaybeRef

function useAverage(array: MaybeComputedRef<MaybeComputedRef<number>[]>) {
  return computed(() => {
    const arr = resolveUnref(array)
    return arr.reduce((sum, cur) => sum + resolveUnref(cur), 0) / arr.length
  })
}

写到这里,就好像已经很符合要求了,而且补充了 test cases,就提交了 pr。有趣的是作者还对这种处理参数的方式表示了赞同,所以这个pr很快就被合并 了。

由于当时各个pr对于函数参数的处理各部相同,在正式发布的时候,作者就将这种动态参数再次做了一个抽象,见 refactor(math): extract common utils,新增支持同时传入多个 单个响应式变量 作为参数。

import type { MaybeComputedRef } from '@vueuse/shared'
import { resolveUnref } from '@vueuse/shared'

export type MaybeComputedRefArgs<T> = MaybeComputedRef<T>[] | [MaybeComputedRef<MaybeComputedRef<T>[]>]

export function resolveUnrefArgsFlat<T>(args: MaybeComputedRefArgs<T>): T[] {
  return args
    .flatMap((i: any) => {
      const v = resolveUnref(i)
      if (Array.isArray(v))
        return v.map(i => resolveUnref(i))
      return [v]
    })
}

函数的实现也变成了

export function useAverage(array: MaybeComputedRef<MaybeComputedRef<number>[]>): ComputedRef<number>
export function useAverage(...args: MaybeComputedRef<number>[]): ComputedRef<number>

/**
 * Get the average of an array reactively
 *
 * @see https://vueuse.org/useAverage
 */
export function useAverage(...args: MaybeComputedRefArgs<number>): ComputedRef<number> {
  return computed(() => {
    const array = resolveUnrefArgsFlat(args)
    return array.reduce((sum, v) => sum += v, 0) / array.length
  })
}

再看下test case

it('should work with rest', () => {
    const a = ref(1)
    const b = ref(2)
    const sum = useAverage(a, () => b.value, 3)
    expect(sum.value).toBe(2)
    b.value = 11
    expect(sum.value).toBe(5)
    a.value = 10
    expect(sum.value).toBe(8)
})

这样的话,就同时支持了单个参数和多个参数了,功能更加完整了。


Profile picture

Written by Colgin who lives and works in China, focus on web development. You can comment on github