Typescript: Coliziuni de nume de proprietăți în tipuri vs interfețe


Să zicem că am 2 tipuri cu același nume de proprietate, dar cu tipuri de date diferite:

type A = {
  myProperty:string
}

type B = {
  myProperty:number
}

Dacă extind ambele folosind o interfață, văd o eroare, așa cum mă așteptam:

interface C extends A, B {}
Interface 'C' cannot simultaneously extend types 'A' and 'B'.
  Named property 'myProperty' of types 'A' and 'B' are not identical.

Dar dacă folosesc un tip pentru a extinde ambele, nu există nicio excepție. În acest caz, care este tipul de date rezultat al myProperty? Există o modalitate de a verifica tipul de date rezultat?

type C = A & B; // no error

Nu pot să stabilesc o valoare pentru myProperty (șir de caractere sau număr) dacă C este declarat ca un tip. Care este cea mai bună modalitate de a gestiona acest scenariu?

function F(myParameter:C) {
  console.log(myParameter);
}

F({myProperty: 90});
(property) myProperty: never
Type 'number' is not assignable to type 'never'.ts(2322)
index.ts(3, 3): The expected type comes from property 'myProperty' which is declared here on type 'C'

Aceeași eroare dacă apelez funcția ca F({myProperty: 'test data'});.

Loc de joacă: https://codesandbox.io/s/typescript-playground-export-forked-dglbq5?file=/index.ts

Răspuns #1

Dacă doriți să intersectați două tipuri conflictuale, dar totuși doriți să instanțiați tipul rezultat, există mai multe opțiuni pentru a le gestiona. Notă că ambele soluții nu vor rezolva conflictele pentru proprietățile înzăpezite mai adânc decât primul nivel.

Să presupunem că avem două interfețe A și B pe care vrem să le intersectăm.

interface A {
    a: string
    b: string
}

interface B {
    a: number
    c: string
}

Prima opțiune ar putea fi să se omită proprietatea conflictuală din tipul rezultat.

type IntersectByOmitting<A, B> = {
    [K in keyof (A & B) as K extends keyof A & keyof B 
      ? [A[K] & B[K]] extends [never] 
        ? never 
        : K 
      : K
    ]: K extends keyof A ? A[K] : B[K & keyof B]
}

type T0 = IntersectByOmitting<A, B>
//   ^? type T0 = { b: string; c: string; }

Deoarece a se rezolvă la never în intersecție, va fi omis din tipul rezultat.


Așa cum ai spus în comentariu, s-ar putea să vrei să ai în schimb o uniune a proprietăților conflictuale.

type IntersectByUnion<A, B> = {
    [K in keyof (A & B)]: K extends (keyof A & keyof B) ? 
      [A[K] & B[K]] extends [never] 
        ? A[K] | B[K] 
        : A[K] & B[K]
      : K extends keyof A ? A[K] : B[K & keyof B]
}

type T1 = IntersectByUnion<A, B>
// type T1 = {
//    a: string | number;
//    b: string;
//    c: string;
// }

Tipul lui a este acum string | number.

Teren de joacă