假设我有一个函数,该函数接受一个对象,一个键和一个值,然后返回一个新对象,该对象扩展了原始对象并添加了键和值。
function addKeyValue(obj: Object, key: string, value: any) {
return {
...obj,
[key]: value
}
}
问题: 如何键入此函数,以便按如下方式调用它:
const user = addKeyValue({ name: 'Joe' }, 'age', 30);
编译器知道{user}的类型为{ name: string, age: number }
注意: 我知道我可以在没有函数的情况下执行此操作,但是此问题的目的是通过函数执行。 (此示例基于一个更复杂的问题,为简便起见已进行了简化。)
这就是我在想的,但它不起作用。 :(
function addKeyValue<TInput>(
obj: TInput,
key: string,
value: any): TInput & { [key]: typeof value } {
return {
...obj,
[key]: value
}
}
答案 0 :(得分:2)
从调用者的角度来看,您可能希望签名类似于以下之一:
declare function addKeyValue<T extends {}, K extends keyof any, V>(obj: T, key: K, value: V):
T & Record<K, V>;
const user = addKeyValue({ name: 'Joe' }, 'age', 30); // { name: string; } & Record<"age", number>
user.age // number
user.name // string
这是TypeScript 2.8之前的版本,其中的交集是表示您正在做的事情的最佳方法。您可能对{name: string} & Record<"age", number>
}类型不满意,但是它的行为就像您想要的那样。
您也可以使用conditional types:
declare function addKeyValue2<T extends {}, K extends keyof any, V>(obj: T, key: K, value: V):
{ [P in keyof (T & Record<K, any>)]: P extends K ? V : P extends keyof T ? T[P] : never };
const user2 = addKeyValue2({ name: 'Joe' }, 'age', 30); // { age: number; name: string; }
user2.age // number
user2.name // string
这具有返回类型看起来像您期望的那样的优点,但是缺点是复杂且可能很脆弱(对于联合,其行为可能并不总是如预期的那样)。
此版本的另一个可能的优点是,如果key
与obj
的键重叠,则属性类型将被覆盖:
const other2 = addKeyValue2({ name: 'Joe', age: 30 }, 'name', false);
// { name: boolean; age: number }
而不是相交,这可能不是您想要的:
const whoops = addKeyValue({ name: 'Joe', age: 30 }, 'name', false).name; // never
// boolean & string ==> never
这些都不能正确检查实现类型。为此,您应该使用type assertions或overloads做的任何事情:
return {
...(obj as any), // simplest way to silence the compiler
[key]: value
}
希望有帮助。祝你好运!