TypeScript 3 parameterlista snittyp
TypeScript 3 förbättrade parameterlistor.
Jag tittar på typdefinitionen för Object.assign, det ser ut ungefär så här:
interface ObjectConstructor {
assign<T, U>(target: T, source: U): T & U;
assign<T, U, V>(target: T, source1: U, source2: V): T & U & V;
assign<T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;
...
Jag undrar om vi kan skriva om den metodsignaturen utan överlagringar nu? Det vill säga, kan vi uttrycka att returvärdet är snittet av alla inmatningsargument, eller är det fortfarande inte möjligt?
Jag behöver något väldigt liknande för en funktion jag arbetar med.
Svar #1
Även om jag gillar @y2bd:s svar, tror jag att det kommer att få ett problem om någon av de passade parametrarna själva är unionstyper:
const eitherOr = Math.random() < 0.5 ? { c: "4" } : { c: 5 };
const notSoGood = assign({ a: "2" }, { b: "3" }, eitherOr);
notSoGood.c; // string & number ?!
Du kommer att märka att eitherOr förvandlas från en union till en snitttyp, och notSoGood är inte rätt typ.
Ett sätt runt det är att "boxa" och "unboxa" tupplerna så att du kan skilja mellan enkelparametrar-som-är-unioner och unionen av flera parametertyper:
type BoxedTupleTypes<T extends any[]> =
{ [P in keyof T]: [T[P]] }[Exclude<keyof T, keyof any[]>]
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type UnboxIntersection<T> = T extends { 0: infer U } ? U : never;
declare function assign<T, S extends any[]>(
target: T,
...sources: S
): T & UnboxIntersection<UnionToIntersection<BoxedTupleTypes<S>>>
Nu kommer du att ha rätt typ:
const notSoGood = assign({ a: "2" }, { b: "3" }, eitherOr);
notSoGood.c // string | number
Det sagt vet jag inte om denna version har sina egna problem. För att inte tala om att intersection inte riktigt är vad du vill ändå. Du skulle hellre skriva över egenskaper om möjligt, vilket intersection inte fångar. Så innan vi föreslår att ändra standardbiblioteket kanske vi vill tänka på den bästa signaturen som TypeScript har att erbjuda oss.
Hoppas det hjälper. Lycka till!
Svar #2
Låt oss börja med att omvandla en tuppel av typer till en union av typerna i tuplen:
type TupleTypes<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? V : never;
type A = TupleTypes<[1, "hello", true]>; // === 1 | "hello" | true
Då lånar vi från detta svar för att konvertera från en union av typer till en snitt av typer:
type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never;
type B = UnionToIntersection<A>; // === 1 & "hello" & true
Nu när vi har det, låt oss skriva ett wrapper för Object.assign:
function assign<T extends object, S extends object[]>(target: T, ...sources: S): T & UnionToIntersection<TupleTypes<S>> {
return Object.assign(target, ...sources);
}
const good = assign({ a: "2" }, { b: "3" }, { c: "4" }, { d: "4" }, { g: "5" });
// const good: { a: string; } & { b: string; } & { c: string; } & { d: string; } & { g: string; }
