С точки зрения вызывающего абонента, вы, вероятно, хотите, чтобы подпись была похожа на одну из следующих:
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>
}, но он действует так, как вы хотите.
Вы также можете использовать условные типы :
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
Ничто из этого не делает проверку типа реализации должным образом. Для этого вы должны использовать любые утверждения типа или overloads выполнят работу:
return {
...(obj as any), // simplest way to silence the compiler
[key]: value
}
Надеюсь, это поможет. Удачи!