如何正确键入更新对象指定字段值的函数

时间:2019-09-10 17:45:11

标签: typescript

我已经实现了一个函数,该函数接受对象,回调和包含该对象键的数组。此函数使用参数中提供的回调更新数组中指定的每个字段的值。

示例:

const foo = { a: 3, b: 4, c: 4 }
const bar = updateFields(foo, (val) => val + 10, ['a', 'b'])

console.log(bar) // { a: 13, b: 14, c: 4 }

现在我已经实现并测试了此功能,因此可以正常工作。

const updateFields = <T extends {}, K extends keyof T, V>(
  obj: T,
  callback: (val: T[K]) => V,
  fields: K[]
) =>
  Object.entries<T[K]>(obj).reduce(
    (acc, [key, value]) => {
      if (fields.includes(key as K)) {
        acc[key] = callback(value)
      } else {
        acc[key] = value
      }

      return acc
    },
    {} as any
  )

我感兴趣的是如何正确地为此函数编写类型,尤其是返回类型,因为现在我将其隐式设置为any

1 个答案:

答案 0 :(得分:0)

updateFields的返回类型为{ [P in K]: V } & Omit<T, K>-K中的所有属性fields被更新为值V,而其余的属性被保留。 / p>

reduce操作中,如果一个新对象在另一个属性之后被迭代地构造,则总是需要某种形式的断言/广播。在您的具体示例中,可以通过遍历fields数组而不是T的原始对象属性来简化函数体。这样可以节省key as K强制转换和条件逻辑。

const updateFields = <T extends object, K extends keyof T, V>(
  obj: T,
  callback: (val: T[K]) => V,
  fields: K[]
): Omit<T, K> & { [P in K]: V } => ({
  ...obj,
  ...fields.reduce(
    (acc, cur) => {
      // ... or use spread here
      acc[cur] = callback(obj[cur]);
      return acc;
    },
    {} as { [P in K]: V } // this cast is (almost always) necessary
  )
});

让我们通过为c选择字符串类型来使示例更加明显:

const foo = { a: 3, b: 4, c: "foo" };

// const bar: Pick<{a: number; b: number; c: string}, "c"> & {a: number; b: number;}
const bar = updateFields(foo, val => val + 10, ["a", "b"]);

console.log(bar); // { a: 13, b: 14, c: "foo" }

Playground