Объединить кортеж объектов в один тип объекта - PullRequest
0 голосов
/ 07 марта 2019

Эта функция должна поддерживать любое количество аргументов:

type Result<T extends object[]> = "???"

function merge<T extends object[]>(...sources: T): Result<T> {
  return Object.assign({}, ...sources)
}

Пример ввода с ожидаемым типом результата: (детская площадка)

type Expected = {
  a: 2
  b: 1 | 2
  c?: 1
  d?: 1 | 2
  e: 2
  f: 2
}

// The return type should match `Expected` exactly. No intersections please!
const result: Expected = merge(
  {} as {
    a: 1
    b: 1
    c?: 1
    d?: 1
    e?: 1
  },
  {} as {
    a: 2
    b?: 2
    d?: 2
    e: 2
    f: 2
  }
)

Смежный вопрос: Typescript, объединение типов объектов?

Ответы [ 2 ]

1 голос
/ 07 марта 2019

Короткий ответ: вы не можете сделать это для произвольного числа значений. Немного более длинный ответ заключается в том, что не следует пытаться сделать это, поскольку естественное рекурсивное определение не будет работать и различные способы, которыми вы можете обмануть компилятор, - официально нахмурился .

Если вы готовы поддержать до некоторого разумного, но конечного максимального количества аргументов, вы можете сделать это. Действительно, стандартное определение библиотеки для Object.assign() в настоящее время представляет собой всего лишь несколько перегрузок с пересечениями. И хотя это может когда-нибудь измениться , оно кажется достаточно хорошим для людей.

Предполагая, что мы берем Spread<L, R> из другого ответа в качестве отправной точки, мы можем сделать наш собственный SpreadTuple, который работает для всего, вплоть до фиксированной длины:

type Tail<L extends any[]> =
  ((...l: L) => void) extends ((h: infer H, ...t: infer T) => void) ? T : never;

type SpreadTuple<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple1<Tail<T>>>
type SpreadTuple1<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple2<Tail<T>>>
type SpreadTuple2<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple3<Tail<T>>>
type SpreadTuple3<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple4<Tail<T>>>
type SpreadTuple4<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple5<Tail<T>>>
type SpreadTuple5<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple6<Tail<T>>>
type SpreadTuple6<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple7<Tail<T>>>
type SpreadTuple7<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple8<Tail<T>>>
type SpreadTuple8<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple9<Tail<T>>>
type SpreadTuple9<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTupleX<Tail<T>>>
type SpreadTupleX<T extends {}[]> = T[number]; // give up

Я сделал это таким образом, чтобы вы могли видеть, как легко расширить его до любой длины, которая вас волнует. Вы можете сделать это без Tail, если вам удобно жестко программировать Spread<Spread<Spread<....>>>.

В любом случае, теперь это работает:

// use default parameter R to expand result to easy-to-digest type
function merge<T extends object[], R = SpreadTuple<T>>(...sources: T): { [K in keyof R]: R[K] } {
  return Object.assign({}, ...sources);
}

const result: Expected = merge(
  {} as {
    a: 1
    b: 1
    c?: 1
    d?: 1
    e?: 1
  },
  {} as {
    a: 2
    b?: 2
    d?: 2
    e: 2
    f: 2
  }
)
//const result: {
//  c?: 1 | undefined;
//  a: 2;
//  e: 2;
//  f: 2;
//  b: 1 | 2;
//  d: 1 | 2 | undefined;
//}

И давайте просто попробуем один с более чем двумя аргументами:

const r = merge({ a: 1, b: 2 }, { b: "3", c: "4" }, { c: true, d: false });
// {  a: number;  b: string;  c: boolean;  d: boolean; }

Хорошо выглядит для меня.

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

0 голосов
/ 07 марта 2019
type Result<T extends object[]> = UnionToIntersection<T[number]>

/**
 * @author https://stackoverflow.com/users/2887218/jcalz
 * @see https://stackoverflow.com/a/50375286/10325032
 */
type UnionToIntersection<Union> =
  (Union extends any
    ? (argument: Union) => void
    : never
  ) extends (argument: infer Intersection) => void
      ? Intersection
      : never;

См. Игровая площадка TypeScript .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...