Короткий ответ: вы не можете сделать это для произвольного числа значений. Немного более длинный ответ заключается в том, что не следует пытаться сделать это, поскольку естественное рекурсивное определение не будет работать и различные способы, которыми вы можете обмануть компилятор, - официально нахмурился .
Если вы готовы поддержать до некоторого разумного, но конечного максимального количества аргументов, вы можете сделать это. Действительно, стандартное определение библиотеки для 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; }
Хорошо выглядит для меня.
Надеюсь, это поможет. Удачи!