TypeScript - добавить динамически именованное свойство в возвращаемый тип - PullRequest
0 голосов
/ 06 сентября 2018

Допустим, у меня есть функция, которая принимает объект, ключ и значение, а затем возвращает новый объект, который расширяет исходный объект, добавляя ключ и значение.

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

1 Ответ

0 голосов
/ 06 сентября 2018

С точки зрения вызывающего абонента, вы, вероятно, хотите, чтобы подпись была похожа на одну из следующих:

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
  }

Надеюсь, это поможет. Удачи!

...