Как я могу указать TS, что каждое свойство в аргументе должно отображаться на свойство возвращаемого типа? - PullRequest
2 голосов
/ 27 апреля 2020

Я пытаюсь написать функцию main, которая принимает объект opts и возвращает другой объект. opts имеет два необязательных свойства a и b.

Если присутствует a, возвращаемый объект должен иметь свойство aResult. Если присутствует b, возвращаемый объект должен иметь свойство bResult. Если a и b присутствуют в opts, то и aResult и bResult должны присутствовать в возвращаемом объекте.

Я могу добиться этого с перегрузками:

interface usesA {
    a: string;
}

interface usesB {
    b: string;
}

interface aReturnType {
    aResult: string;
}

interface bReturnType {
    bResult: string;
}

function main(opts: usesA): aReturnType;
function main(opts: usesB): bReturnType
function main(opts: usesA & usesB): aReturnType & bReturnType; 
// implementation omitted

Но это становится довольно скучным, если я хочу добавить дополнительные свойства: c, который отображается на cResult, d, который отображается на dResult et c.

Есть ли более лаконичный способ сделать это, например, с помощью универсальной c функции?

1 Ответ

2 голосов
/ 28 апреля 2020

Имея такой интерфейс, как:

// Mapping of argument types to return types
interface MyMapping {
  a: { aResult: string }
  b: { bResult: string }
}

Вы определенно можете сделать несколько магов c.

Сначала вам понадобится тип аргумента. Это будет каждый ключ из опционально включенного сопоставления со значением string.

// Type of arguments. Each key in the mapping has a string value.
type UsesArgs<T> = { [K in keyof T]?: string }

Получить тип возврата немного сложнее. В основном вы хотите отобразить каждое свойство аргумента, а затем найти этот тип в отображении. Обратите внимание, что при выполнении итерации нам необходимо обеспечить, чтобы ключи имели тип keyof Args & keyof Mapping, поскольку мы хотим быть уверены, что каждый ключ присутствует в обоих типах.

// Type of each possible return value, as a union of all types.
type ReturnValueTypeUnion<Args, Mapping> = {
  // for each key Args and Mapping have in common, get the type from mapping
  [K in keyof Args & keyof Mapping]: Mapping[K]

// Get values from mapping as a union
}[keyof Args & keyof Mapping]

Проблема заключается в том, что создается объединение { a: string } | { b : string } , но то, что мы на самом деле хотим, это пересечение { a: string } & { b: string }. К счастью, этот ответ имеет вспомогательный тип для этого.

// Convert a union to an intersection 
intersection-type
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends (k: infer I) => void
  ? I
  : never

Теперь давайте создадим тип, чтобы собрать все это вместе:

// Type of the return values, as an intersection.
type ReturnValueType<Args, Mapping> = 
  UnionToIntersection<ReturnValueTypeUnion<Args, Mapping>>

И мы можем наконец, введите функцию:

declare function main<T extends UsesArgs<MyMapping>>(
  opts: T,
): ReturnValueType<T, MyMapping>

// Examples
const a = main({ a: 'abc' }) // { aResult: string }
const b = main({ b: 'def' }) // { bResult: string }
const aPlusB = main({ a: 'abc', b: 'def' }) // { aResult: string } & { bResult: string }

Детская площадка

...