Не вдається викликати вираз, у типі якого відсутній підпис виклику


77

У мене є яблуко та груші - обидва мають isDecayedатрибут:

interface Apple {
    color: string;
    isDecayed: boolean;
}

interface Pear {
    weight: number;
    isDecayed: boolean;
}

І обидва типи можуть бути в моїй кошику з фруктами (кілька разів):

interface FruitBasket {
   apples: Apple[];
   pears: Pear[];
}

Припустимо, поки що мій кошик порожній:

const fruitBasket: FruitBasket = { apples: [], pears: [] };

Тепер ми випадково дістаємо з кошика один вид:

const key: keyof FruitBasket = Math.random() > 0.5 ? 'apples': 'pears'; 
const fruits = fruitBasket[key];

І звичайно, ніхто не любить гнилі плоди, тому ми збираємо лише свіжі:

const freshFruits = fruits.filter((fruit) => !fruit.isDecayed);

На жаль, машинопис говорить мені:

Не вдається викликати вираз, у типі якого відсутній підпис виклику. Введіть '((callbackfn: (значення: Apple, індекс: число, масив: Apple []) => будь-який, thisArg ?: будь-який) => Apple []) | ... 'не має сумісних підписів дзвінків.

Що тут не так - просто Typescript не любить свіжих фруктів або це помилка Typescript?

Ви можете спробувати самі в офіційному Typescript Repl .



1
З будь-якої причини ви не можете розширити загальний Fruitінтерфейс із isDecayedвластивістю, а потім оголосити фрукти типовими Fruit[]?
Gerrit0

дивно ... немає помилки, якщо ви явно встановили key для stringieconst key: string = Math.random() > 0.5 ? 'apples': 'pears';
shusson

^ трапляється тому, що без явного підпису індексу типом повернення буде той, anyякий обходить проблему.
shusson

Відповіді:


87

TypeScript підтримує структурну типізацію (яку також називають качиною), тобто типи сумісні, коли вони мають однакові члени . Ваша проблема полягає в тому , що Appleі Pearне поділяє всі свої член, а це значить , що вони не сумісні. Однак вони сумісні з іншим типом, який має лише isDecayed: booleanчлен. Через структурну типізацію вам не потрібно успадковувати Appleта Pearвід такого інтерфейсу.

Існують різні способи призначення такого сумісного типу:

Призначити тип під час оголошення змінної

Це твердження неявно вводиться для Apple[] | Pear[]:

const fruits = fruitBasket[key];

Ви можете просто використовувати сумісний тип явно в декларації змінної:

const fruits: { isDecayed: boolean }[] = fruitBasket[key];

Для додаткового повторного використання ви також можете спочатку визначити тип, а потім використовувати його у своїй декларації (зауважте, що інтерфейси Appleта та Pearінтерфейси змінювати не потрібно):

type Fruit = { isDecayed: boolean };
const fruits: Fruit[] = fruitBasket[key];

Відлийте до сумісного типу для операції

Проблема даного рішення полягає в тому, що воно змінює тип fruits змінної. Це може бути не те, що ти хочеш. Щоб уникнути цього, ви можете перед операцією звузити масив до сумісного типу, а потім встановити тип назад на той самий тип, що і fruits:

const fruits: fruitBasket[key];
const freshFruits = (fruits as { isDecayed: boolean }[]).filter(fruit => !fruit.isDecayed) as typeof fruits;

Або з Fruitтипом багаторазового використання :

type Fruit = { isDecayed: boolean };
const fruits: fruitBasket[key];
const freshFruits = (fruits as Fruit[]).filter(fruit => !fruit.isDecayed) as typeof fruits;

Перевага цього рішення полягає в тому, що і те, fruitsі інше freshFruitsбуде мати тип Apple[] | Pear[].


6

Як згадувалося у випуску github, який спочатку посилав @peter в коментарях:

const freshFruits = (fruits as (Apple | Pear)[]).filter((fruit: (Apple | Pear)) => !fruit.isDecayed);

2
У мене немає змішаного масиву яблук та груш, але масив яблук чи масив груш.
Ердем

1
Причина, з якої я використовую Typescript: Для структурування мого коду я або третя особа краще розуміємо, що робить код і з чим має справу код. І змішаний масив яблук та груш - це не те, що коли-небудь буде в цьому масиві. То чому я повинен визначати це як змішаний масив?
Ердем

Виправте мене, якщо я помиляюся, але твердження, що масив буде містити яблука або груші, не є хибним, це просто не опис усієї істини.
shusson

1
Ви абсолютно праві. Але якщо у мене є можливість описати його більш детально, я хочу використати цей метод, і я не розумію, чому Typescript обробляє більш детальний як помилку.
Ердем

4

Можливо, створити спільний Fruitінтерфейс, який надає isDecayed. fruitsтепер типу, Fruit[]тому тип може бути явним. Подобається це:

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);

Дякую - Я тим часом обійшов цей спосіб, але все одно це більше схоже на хакерство, ніж на реальне рішення для мене. Це єдиний спосіб?
Ердем,

Це відбувається постійно. Ви також FruitBasketможете бути просто масивом Fruits
Frambot

-3

У мене була та ж проблема з цифрою - бібліотека JS. Виправлення полягало в тому, щоб знову встановити типізацію за допомогою цієї команди:

npm install --save @types/numeral
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.