typescript - Finns det något sätt att få snittypen av generiska restparametrar?


Jag skriver en sammanslagningsfunktion som slår ihop flera objekt och returnerar.

type A = { a: number };
type B = { b: number };
type C = { c: number };

const a: A = { a: 1 };
const b: B = { b: 2 };
const c: C = { c: 3 };

function merge<T extends any[]>(...args: T): { [k in keyof T]: T[k] } {
    return args.reduce((previous, current) => {
        return Object.assign(previous, current);
    });
}

const m = merge(a, b, c);

m.a;
m.b;
m.c;

Vad jag förväntar mig för typen av m är A & B & C, men jag fick [A, B, C] i kompilatorn, och det gav mig felet.

Property 'a' does not exist on type '[A, B, C]'.ts(2339)

Finns det ett rätt sätt att deklarera returtypen för min merge-funktion?

Svar #1

Det du behöver är en korsning av elementen i arrayen:

TS Lekplats

type ArrayIntersection<T extends readonly unknown[]> = T extends [infer Head, ...infer Rest] ?
  Head & ArrayIntersection<Rest>
  : unknown;

function merge <T extends readonly any[]>(...args: T): ArrayIntersection<T> {
  return args.reduce((previous, current) => {
    return Object.assign(previous, current);
  });
}

type A = { a: number };
type B = { b: number };
type C = { c: number };

declare const a: A;
declare const b: B;
declare const c: C;

const m = merge(a, b, c); // A & B & C

m.a; // number
m.b; // number
m.c; // number

Observera att korsande typer som är inkompatibla (eller har medlemmar vars typer är inkompatibla) kommer att resultera i never för den typen:

declare const result: ArrayIntersection<[
  { a: string },
  { b: number },
  { person: { first: string } },
  { person: { last: string } },
  { b: string },
]>;

result.person; // { first: string; last: string; }
result.a; // string
result.b; // never

Svar #2

T[number] kommer att producera en union av typerna i args-arrayen, och sedan kan du använda en Union till Intersection-typ för att slå samman medlemmarna i den unionen till en intersection:

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

function merge<T extends any[]>(...args: T): UnionToIntersection<T[number]> {
    return args.reduce((previous, current) => {
        return Object.assign(previous, current);
    });
}

Lekplats

Relaterade artiklar