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


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.