首页 > 解决方案 > How correctly type function that updates values of specified fields of object

问题描述

I've implemented a function that accepts object, callback and array containing keys of this object. This function updates value of every field specified in the array using callback provided in arguments.

Example:

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 }

Right now I have this function implemented and tested, so it works ok.

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
  )

What I'm interested in is how to correctly write types for this function, especially the return type, as right now I have it implicitly set to any.

标签: typescript

解决方案


updateFields will have return type { [P in K]: V } & Omit<T, K> - all properties K in fields are updated to value V while the rest of the properties is preserved.

In reduce operations there is always some form of assertion/cast necessary, if a new object is iteratively constructed one property after the other. In your concrete example you can ease up the function body by iterating over the fields array, not the original object properties of T. That will save you the key as K cast and conditional logic.

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
  )
});

Let's make the example more obvious by choosing a string type for 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


推荐阅读