Ein Ausdruck, dessen Typ keine Anrufsignatur hat, kann nicht aufgerufen werden

76

Ich habe Apfel und Birnen - beide haben ein isDecayedAttribut:

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

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

Und beide Sorten können (mehrmals) in meinem Obstkorb sein:

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

Nehmen wir an, mein Korb ist vorerst leer:

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

Jetzt nehmen wir zufällig eine Sorte aus dem Korb:

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

Und natürlich mag niemand verfallene Früchte, also pflücken wir nur die frischen:

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

Leider sagt mir Typescript:

Ein Ausdruck, dessen Typ keine Anrufsignatur hat, kann nicht aufgerufen werden. Geben Sie '((callbackfn: (Wert: Apple, Index: Nummer, Array: Apple []) => any, thisArg?: Any) => Apple []) | ein ... 'hat keine kompatiblen Rufsignaturen.

Was ist hier falsch - ist es nur so, dass Typescript keine frischen Früchte mag oder ist dies ein Typescript-Fehler?

Sie können es selbst im offiziellen Typescript Repl versuchen .

Erdem
quelle
Nachdem ich versucht habe, Google Github.com/Microsoft/TypeScript/issues/7960
Peterchaula
1
Gibt es einen Grund, warum Sie eine generische FruitSchnittstelle mit der isDecayedEigenschaft nicht erweitern und dann Früchte als vom Typ deklarieren können Fruit[]?
Gerrit0
seltsam ... es gibt keinen Fehler, wenn Sie das explizit keyauf stringieconst key: string = Math.random() > 0.5 ? 'apples': 'pears';
shusson
^ passiert, weil ohne eine explizite Indexsignatur der Rückgabetyp anydas Problem umgeht.
Shusson

Antworten:

87

TypeScript unterstützt die strukturelle Typisierung (auch als Ententypisierung bezeichnet). Dies bedeutet, dass Typen kompatibel sind, wenn sie dieselben Mitglieder verwenden . Ihr Problem ist das Appleund Pearteilen Sie nicht alle ihre Mitglieder, was bedeutet, dass sie nicht kompatibel sind. Sie sind jedoch mit einem anderen Typ kompatibel, der nur das isDecayed: booleanMitglied hat. Aufgrund der strukturellen Typisierung, Sie don‘Notwendigkeit zu erben Appleund Pearvon einer solchen Schnittstelle.

Es gibt verschiedene Möglichkeiten, einen solchen kompatiblen Typ zuzuweisen:

Typ während der Variablendeklaration zuweisen

Diese Anweisung lautet implizit Apple[] | Pear[]:

const fruits = fruitBasket[key];

Sie können einfach einen kompatiblen Typ explizit in Ihrer Variablendeklaration verwenden:

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

Für zusätzliche Wiederverwendbarkeit können Sie den Typ auch zuerst definieren und dann in Ihrer Deklaration verwenden (beachten Sie, dass die Schnittstellen Appleund Pearnicht geändert werden müssen):

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

Für die Operation auf einen kompatiblen Typ umwandeln

Das Problem mit der angegebenen Lösung besteht darin, dass der Typ der fruitsVariablen geändert wird. Dies ist möglicherweise nicht das, was Sie wollen. Um dies zu vermeiden, können Sie das Array vor der Operation auf einen kompatiblen Typ eingrenzen und den Typ dann auf denselben Typ zurücksetzen wie fruits:

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

Oder mit dem wiederverwendbaren FruitTyp:

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

Der Vorteil dieser Lösung besteht darin, dass beide fruitsund freshFruitsvom Typ sind Apple[] | Pear[].

Sefe
quelle
6

Wie in der Github-Ausgabe erwähnt, die ursprünglich von @peter in den Kommentaren verlinkt wurde:

const freshFruits = (fruits as (Apple | Pear)[]).filter((fruit: (Apple | Pear)) => !fruit.isDecayed);
Shusson
quelle
2
Ich habe keine gemischte Reihe von Apfel und Birnen, sondern eine Reihe von Äpfeln oder eine Reihe von Birnen.
Erdem
1
Der gleiche Grund, warum ich Typescript verwende: Um meinen Code zu strukturieren, kann ich oder eine dritte Person besser verstehen, was der Code tut und womit sich der Code befasst. Und eine gemischte Reihe von Äpfeln und Birnen ist nicht das, was jemals in dieser Reihe sein wird. Warum sollte ich es als gemischtes Array definieren?
Erdem
Korrigieren Sie mich, wenn ich falsch liege, aber die Aussage, dass das Array entweder Äpfel oder Birnen enthält, ist nicht falsch, sondern beschreibt nicht die gesamte Wahrheit.
Shusson
1
Du hast absolut recht. Aber wenn ich die Möglichkeit habe, es detaillierter zu beschreiben, möchte ich diese Methode verwenden, und dies ist der Punkt, an dem ich nicht verstehe, warum Typescript die detailliertere als Fehler behandelt.
Erdem
4

Erstellen Sie möglicherweise eine gemeinsam genutzte FruitSchnittstelle, die isDecayed bereitstellt. fruitsist jetzt vom Typ, Fruit[]sodass der Typ explizit sein kann. So was:

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);
Frambot
quelle
Danke - das ist die Problemumgehung, die ich in der Zwischenzeit verwendet habe, aber es fühlt sich für mich immer noch eher nach einem Hack als nach einer echten Lösung an. Ist das der einzige Weg?
Erdem
Das passiert die ganze Zeit. Sie FruitBasketkönnten auch nur eine Reihe von Fruits sein
Frambot
-2

Ich hatte das gleiche Problem mit numeral, einer JS-Bibliothek. Das Update bestand darin, die Typisierungen mit diesem Befehl erneut zu installieren:

npm install --save @types/numeral
thewindev
quelle