我已经实现了一个函数,该函数接受对象,回调和包含该对象键的数组。此函数使用参数中提供的回调更新数组中指定的每个字段的值。
示例:
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
。
答案 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" }