Tengo manzanas y peras, ambas tienen un atributo isDecayed :
interface Apple { color: string; isDecayed: boolean; } interface Pear { weight: number; isDecayed: boolean; }Y ambos tipos pueden estar en mi canasta de frutas (varias veces):
interface FruitBasket { apples: Apple[]; pears: Pear[]; }Supongamos que por ahora mi cesta está vacía:
const fruitBasket: FruitBasket = { apples: [], pears: [] };Ahora sacamos al azar un tipo de la cesta:
const key: keyof FruitBasket = Math.random() > 0.5 ? 'apples': 'pears'; const fruits = fruitBasket[key];Y, por supuesto, a nadie le gustan las frutas podridas, así que solo recogemos las frescas:
const freshFruits = fruits.filter((fruit) => !fruit.isDecayed);Desafortunadamente mecanografiado me dice:
No se puede invocar una expresión cuyo tipo carece de firma de llamada. Escriba '((callbackfn: (valor: Apple, índice: número, matriz: Apple[]) => any, thisArg?: any) => Apple[]) | ...' no tiene firmas de llamadas compatibles.
¿Qué está mal aquí? ¿Es solo que a Typescript no le gustan las frutas frescas o es un error de Typescript?
Puede probarlo usted mismo en el Typescript Repl oficial.
TypeScript admite la tipificación estructural (también denominada tipificación pato), lo que significa que los tipos son compatibles cuando comparten los mismos miembros . Tu problema es que Apple y Pear no comparten todos sus miembros, lo que significa que no son compatibles. Sin embargo, son compatibles con otro tipo que solo tiene el miembro isDecayed: boolean Debido a la tipificación estructural, no necesita heredar Apple y Pear de dicha interfaz.
Hay diferentes formas de asignar un tipo compatible:
Asignar tipo durante la declaración de variables
Esta declaración se escribe implícitamente en Apple[] | Pear[] :
const fruits = fruitBasket[key];Simplemente puede usar un tipo compatible explícitamente en su declaración de variable:
const fruits: { isDecayed: boolean }[] = fruitBasket[key]; Para una reutilización adicional, también puede definir primero el tipo y luego usarlo en su declaración (tenga en cuenta que no es necesario cambiar las interfaces de Apple y Pear ):
type Fruit = { isDecayed: boolean }; const fruits: Fruit[] = fruitBasket[key];Cast a tipo compatible para la operación
El problema con la solución dada es que cambia el tipo de la variable fruits . Puede que esto no sea lo que quieres. Para evitar esto, puede reducir la matriz a un tipo compatible antes de la operación y luego volver a establecer el tipo en el mismo tipo que las fruits :
const fruits: fruitBasket[key]; const freshFruits = (fruits as { isDecayed: boolean }[]).filter(fruit => !fruit.isDecayed) as typeof fruits; O con el tipo Fruit reutilizable:
type Fruit = { isDecayed: boolean }; const fruits: fruitBasket[key]; const freshFruits = (fruits as Fruit[]).filter(fruit => !fruit.isDecayed) as typeof fruits; La ventaja de esta solución es que tanto las fruits como las frutas freshFruits serán del tipo Apple[] | Pear[] .
Como se mencionó en el problema de github vinculado originalmente por @peter en los comentarios:
const freshFruits = (fruits as (Apple | Pear)[]).filter((fruit: (Apple | Pear)) => !fruit.isDecayed);Tal vez cree una interfaz Fruit compartida que proporcione isDecayed. fruits ahora es de tipo Fruit[] por lo que el tipo puede ser explícito. Me gusta esto:
interface Fruit { isDecayed: boolean; } interface Apple extends Fruit { color: string; } interface Pear extends Fruit { weight: number; } interface FruitBasket { apples: Apple[]; pears: Pear[]; } const fruitBasket: FruitBasket = { apples: [], pears: [] }; const key: keyof FruitBasket = Math.random() > 0.5 ? 'apples': 'pears'; const fruits: Fruit[] = fruitBasket[key]; const freshFruits = fruits.filter((fruit) => !fruit.isDecayed);