Mecanografiado: combinación de tipos en profundidad

Un tutorial paso a paso sobre cómo escribir un tipo genérico en TypeScript que combina estructuras clave-valor anidadas arbitrarias.

Nota del traductor: intencionalmente no traduje algunas palabras (como genérico, clave-valor) porque, en mi opinión, esto solo complicará la comprensión del material.

TLDR:

El código fuente de DeepMergeTwoTypesestará al final del artículo. Cópialo en tu IDE para jugar con él.

Cómo se ve en vsCode:

, generic- TypeScript, (Miniminalist Typescript - Generics)

IDE (. : TypeScript Playground ).

Disclaimer

production ( , ).

&- Typescript

. A B C, A & B

type A = { key1: string, key2: string }
type B = { key2: string, key3: string }
type C = A & B
const a = (c: C) => c.

, .

type A = { key1: string, key2: string }
type B = { key2: null, key3: string }
type C = A & B

A key2 , B null.

Typescript never C . - :

type ExpectedType = {
  key1: string | null,
  key2: string,
  key3: string
}

generic-, Typescript. 2 generic-.

GetObjDifferentKeys<>

type GetObjDifferentKeys<T, U> = Omit<T, keyof U> & Omit<U, keyof T>

2 , A B.

type A = { key1: string, key2: string }
type B = { key2: null, key3: string }

type C = GetObjDifferentKeys<A, B>['']

GetObjSameKeys<>

generic- , , .

type GetObjSameKeys<T, U> = Omit<T | U, keyof GetObjDifferentKeys<T, U>>

— .

type A = { key1: string, key2: string }
type B = { key2: null, key3: string }
type C = GetObjSameKeys<A, B>

, generic- DeepMergeTwoTypes

DeepMergeTwoTypes<>

type DeepMergeTwoTypes<T, U> =
  // " " ()  - 
  Partial<GetObjDifferentKeys<T, U>>
  //   - 
  & { [K in keyof GetObjSameKeys<T, U>]: T[K] | U[K] }

generic " " T U, (). Partial<>, Typescript. ( &-) T U , T[K] | U[K].

. generic "-" (?), .

type A = { key1: string, key2: string }
type B = { key2: null, key3: string }
const fn = (c: DeepMergeTwoTypes<A, B>) => c.

 DeepMergeTwoTypes generic . generic  MergeTwoObjects   DeepMergeTwoTypes  , .

//  generic   DeepMergeTwoTypes<>
type MergeTwoObjects<T, U> =
  // " " ()  - 
  Partial<GetObjDifferentKeys<T, U>>
  //   - 
  & {[K in keyof GetObjSameKeys<T, U>]: DeepMergeTwoTypes<T[K], U[K]>}

export type DeepMergeTwoTypes<T, U> =
  //     ,    
  [T, U] extends [{ [key: string]: unknown }, { [key: string]: unknown } ]
    ? MergeTwoObjects<T, U>
    : T | U

PRO TIP: , DeepMergeTwoTypes if-else (extends ?:) T U , (tuple) [T, U]. &&- Javascript.

generic ,  { [key: string]: unknown } ( Object). ,  MergeTwoObject<>. .

: extends { [key: string]: unknown } -, .. , , booleans ...

! generic . :

type A = { key: { a: null, c: string} }
type B = { key: { a: string, b: string} }
const fn = (c: MergeTwoObjects<A, B>) => c.key.

?

, . generic .

, , infer (to infer - ).

infer  ( ).  infer  (Type inference in conditional types).

infer. (Item):

export type ArrayElement<A> = A extends (infer T)[] ? T : never

// Item === (number | string)
type Item = ArrayElement<(number | string)[]>

, , .  DeepMergeTwoTypes  .

export type DeepMergeTwoTypes<T, U> =
  // ----- 2   ------
  //  ⏬
  [T, U] extends [(infer TItem)[], (infer UItem)[]]
    // ...   ⏬
    ? DeepMergeTwoTypes<TItem, UItem>[]
    : ... rest of previous generic ...

DeepMergeTwoTypes , .

type A = [{ key1: string, key2: string }]
type B = [{ key2: null, key3: string }]
const fn = (c: DeepMergeTwoTypes<A, B>) => c[0].

! ?

... . Nullable non-nullable.

type A = { key1: string }
type B = { key1: undefined }

type C = DeepMergeTwoTypes<A, B>['key']

string | undefined, . if-else .

export type DeepMergeTwoTypes<T, U> =
  [T, U] extends [(infer TItem)[], (infer UItem)[]]
    ? DeepMergeTwoTypes<TItem, UItem>[]
    : [T, U] extends [{ [key: string]: unknown}, { [key: string]: unknown } ]
      ? MergeTwoObjects<T, U>
      // ----- 2   ------
      //  ⏬
      : [T, U] extends [
          { [key: string]: unknown } | undefined, 
          { [key: string]: unknown } | undefined 
        ]
        // ...   ⏬
        ? MergeTwoObjects<NonNullable<T>, NonNullable<U>> | undefined
          : T | U

nullable :

type A = { key1: string }
type B = { key1: undefined }


const fn = (c: DeepMergeTwoTypes<A, B>) => c.key1;

... !

! nullable , .

generic :

type A = { key1: { a: { b: 'c'} }, key2: undefined }
type B = { key1: { a: {} }, key3: string }


const fn = (c: DeepMergeTwoTypes<A, B>) => c.

:

/**
 *  2  T  U    ,   
 * .   `DeepMergeTwoTypes`
 */
type GetObjDifferentKeys<T, U> = Omit<T, keyof U> & Omit<U, keyof T>
/**
 *  2  T and U       
 *   `DeepMergeTwoTypes`
 */
type GetObjSameKeys<T, U> = Omit<T | U, keyof GetObjDifferentKeys<T, U>>
type MergeTwoObjects<T, U> =
  // " "  
  Partial<GetObjDifferentKeys<T, U>>
  //       `DeepMergeTwoTypes<...>`
  & { [K in keyof GetObjSameKeys<T, U>]: DeepMergeTwoTypes<T[K], U[K]> }

//  2 
export type DeepMergeTwoTypes<T, U> =
  //     ,    
  //  
  [T, U] extends [(infer TItem)[], (infer UItem)[]]
    ? DeepMergeTwoTypes<TItem, UItem>[]
    //    
    : [T, U] extends [
         { [key: string]: unknown}, 
         { [key: string]: unknown } 
      ]
      ? MergeTwoObjects<T, U>
      : [T, U] extends [
          { [key: string]: unknown } | undefined, 
          { [key: string]: unknown } | undefined 
        ]
        ? MergeTwoObjects<NonNullable<T>, NonNullable<U>> | undefined
          : T | U

// :
type A = { key1: { a: { b: 'c'} }, key2: undefined }
type B = { key1: { a: {} }, key3: string }

const fn = (c: DeepMergeTwoTypes<A, B>) => c.key

¿Cómo puedo arreglar el  DeepMergeTwoTypes<T, U> genérico para que pueda tomar  N argumentos en lugar de dos?

Dejaré esto para el próximo artículo, pero puedes ver mi borrador de trabajo aquí ).

Nota del traductor

Esta es mi primera experiencia de traducción. Le rogamos que escriba un mensaje personal sobre errores tipográficos, comas y frases simples.




All Articles