Búsqueda de sitios web

typescript - A extiende B; El tipo 'Pick> & B' no se puede asignar al tipo 'A'.ts(2322)


¿Es esto un error o estoy malinterpretando las cosas de TypeScript?

Código de ejemplo a continuación:

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

const func = <A extends B, B>() => {
  const aWithoutB: Omit<A, keyof B> = {} as any; // The {} assignment isn't important here.

  const b: B = {} as any;

  const a: A = {
    ...aWithoutB,
    ...b
  }; // warning here
};

Advertencia del IDE (VS Code) con TypeScript 3.4.5:

Type 'Pick<A, Exclude<keyof A, keyof B>> & B' is not assignable to type 'A'.ts(2322)

Creo que los 2 tipos son iguales pero no. ¿Cuál es la razón?

Respuesta #1

La razón principal es que el compilador actualmente no realiza el tipo de análisis de tipo de orden superior necesario para que pueda entender una equivalencia entre un tipo de objeto genérico no resuelto y las intersecciones de conjuntos complementarios de propiedades de ese tipo. No está claro, según el estado del problema vinculado, si algún día se implementará tal análisis; sin duda es posible hacer que el compilador tenga en cuenta tales cosas, pero ¿quién sabe si se puede hacer sin degradar el rendimiento del compilador?

Una razón secundaria es que esos dos tipos no son realmente equivalentes. Considere las siguientes interfaces Beta y Alpha:

interface Beta {
    x: string | number,
    y: boolean,
    z?: object  
}

interface Alpha extends Beta {
    x: string,
    y: true,
    z: undefined
}

func<Alpha, Beta>(); // no error, but uh oh

declare const beta: Beta;
declare const alpha: Alpha;
const notAlpha: Alpha = { ...alpha, ...beta }; // error!

Tenga en cuenta que Alpha extiende Beta, pero si difunde un Alpha seguido de un Beta en un nuevo objeto, no obtiene un Alpha. Y así, puede llamar a func<Alpha, Beta>(), pero probablemente no debería poder hacerlo.

Sin duda, estabas enfocado en extender un tipo agregándole más propiedades, pero no prestabas atención a extender un tipo restringiendo los tipos de las propiedades existentes.

Podrías endurecer las restricciones en A y B para que cualquier clave compartida por A y B tenga tipos de propiedad mutuamente asignables, especificando que A extends B y que B extends Pick<A, keyof A & keyof B>. Para cualquier clave K en keyof A & keyof B, A extends B implica A[K] extends B[K], y B extends Pick<A, keyof A & keyof B> implica B[K] extends A[K].

Y dentro de la implementación, una vez que estés absolutamente convencido por tus superiores habilidades de razonamiento humano de que Omit<A, keyof B> & B es realmente lo mismo que A (eso espero), pero el compilador no está convencido, puedes imponer tu inteligencia sobre el compilador usando una aserción de tipo.

Veámoslo en acción:

// more strict constraint
const func = <A extends B, B extends Pick<A, keyof A & keyof B>>() => {
    const aWithoutB: Omit<A, keyof B> = {} as any;
    const b: B = {} as any;    
    const a = {
        ...aWithoutB,
        ...b
    } as A; // I'm smarter than the compiler 🤓
};

func<Alpha, Beta>(); // error now, as desired
func<Beta & { extraProp: string }, Beta>(); // okay

Está bien, espero que eso ayude. ¡Buena suerte!

Artículos relacionados