Website-Suche

typescript - Ein Ausdruck, dessen Typ keine Aufrufsignatur hat, kann nicht aufgerufen werden, map


Dieser Code funktioniert:

class A {}
class B {}
class C {}

const classCFromAOrB = (element: A | B): C => new C()

const a: A[] | B[] = [new A()]

const c: C[] = a.map(element => classCFromAOrB(element))

Dieser Code tut dies nicht:

import { classA } from '../some'
import { classB } from './../some'

interface interfaceC {}

const render = (element: classA | classB): interfaceC => {
    return {}
}

interface ResultsScreenProps {
    resultsScreenPresented: boolean
    answers: classA[] | classB[]
    dismiss: SimpleFunc
}

const Screen: React.SFC<ResultsScreenProps> = (props) => {
    const array: classA[] | classB[] = props.answers
    const second: interfaceC[] = array.map(el => render(el)) // here is the error
    ...
}

In der Zeile, die second definiert, erhalte ich einen Fehler:

[ts] Ausdruck kann nicht aufgerufen werden, dessen Typ keine Aufrufsignatur besitzt. Typ '((callbackfn: (value: classA, index: number, array: classA[]) => ...' hat keine kompatiblen Aufrufsignaturen.

Was mache ich falsch?

Der Fehler ist reproduzierbar, wenn classA so aussieht:

class classA {
    anyArg: number

    constructor(anyArg: number) {
        this.anyArg = anyArg
    }
}

Antwort #1

Ab TypeScript 4.3 tritt dieses Problem nicht mehr auf. Der Pull Request bei microsoft/TypeScript#42620 implementierte die Unterstützung für das Aufrufen von map() auf Vereinigungen von Array-Typen.

Playground-Link zum Code

Schon ab TypeScript 3.3 gab es mehr Unterstützung für das Aufrufen von Vereinigungen von Methoden/Funktionen, sodass die folgende Frage und ihre Antwort schon seit einiger Zeit veraltet sind.


Originalantwort für TypeScript 3.2 oder niedriger:

Wie ich in den Kommentaren erwähnt habe, kann man keine Methoden aufrufen, die Union-Typen sind. Die Aufrufsignatur von (classA[] | classB[])['map'] ist

(
  <U>(
    callbackfn: (value: classA, index: number, array: classA[]) => U,
    thisArg?: any
  ) => U[]
) | (
  <U>(
    callbackfn: (value: classB, index: number, array: classB[]) => U,
    thisArg?: any
  ) => U[]
)

Und der Compiler gibt auf. Was Sie tun können, ist, Ihren Typ von (classA[] | classB[]) zu (classA | classB)[] zu erweitern. Erstere bedeutet "entweder ist dies ein Array aus ausschließlich classA-Elementen, oder es ist ein Array aus ausschließlich classB-Elementen", während letztere bedeutet "dies ist ein Array aus Elementen, von denen jedes entweder ein classA oder ein classB ist". Erstere ist spezifischer, da wenn Sie wissen, dass arr[0] ein classA ist, dann wird auch arr[1] ein classA sein... während letztere weniger spezifisch ist, da arr[0] ein classA sein könnte, während arr[1] ein classB sein könnte. Ein Vorteil der letzteren ist, dass (classA | classB)[]['map'] die einzige Signatur hat:

<U>(
  callbackfn: (value: classA | classB, index: number, array: (classA | classB)[]) => U, 
  thisArg?: any
) => U[]

und du kannst das anrufen.


Ihre nächste Frage, „warum hört es auf zu funktionieren, wenn ich irgendetwas in einer der Klassen definiere“, hat mit struktureller Typisierung zu tun. Kurz gesagt, TypeScript denkt, dass classA und classB derselbe Typ sind, wenn sie die gleichen Mitglieder haben. Das mag überraschend sein, da viele andere typisierte Sprachen nominale Typisierung verwenden, bei der zwei Typen mit unterschiedlichen Namen notwendigerweise unterschiedliche Typen sind. Aber TypeScript funktioniert nicht wirklich so.

Wenn classA und classB beide keine Eigenschaften haben, werden sie als gleichwertig zu {}, dem leeren Typ, angesehen. Und (classA[])|(classB[]) reduziert sich dann zu ({}[])|({}[]), was einfach ({}[]) ist. Und das ist keine Vereinigung, daher kann man seine map-Methode aufrufen.

Wenn Sie möchten, dass classA vom Compiler als unterschiedlich zu classB angesehen wird, sollten Sie ihnen unterschiedliche Eigenschaften geben, zumindest bis (und sofern) TypeScript jemals eine bessere nominelle Typisierung unterstützt.


Ich hoffe, das hilft dir. Viel Glück.