【问题标题】:Cannot invoke an expression whose type lacks a call signature, map无法调用其类型缺少调用签名、映射的表达式
【发布时间】:2019-01-05 09:24:06
【问题描述】:

此代码有效:

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

这段代码没有:

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
    ...
}

在定义 second 的行上出现错误:

[ts] 无法调用类型缺少调用签名的表达式。 类型 '((callbackfn: (value: classA, index: number, array: classA[]) =>...' 没有兼容的调用签名。

我做错了什么?

如果 classA 如下所示,则该错误是可重现的:

class classA {
    anyArg: number

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

【问题讨论】:

  • 什么是ResultsScreenProps
  • @ExplosionPills,添加声明
  • 什么是SimpleFunc
  • 顺便说一句,我没有收到此错误;你可能抄错了一些东西。您能否发布导致问题的实际代码?
  • You can't call methods which are union types。将类型从classA[] | classB[] 重新定义为(classA | classB)[],或者在调用map 之前将值扩大到该类型。

标签: typescript


【解决方案1】:

从 TypeScript 4.3 开始,不再出现此问题。 microsoft/TypeScript#42620 的拉取请求实现了对数组类型联合调用 map() 的支持。

Playground link to code

早在 TypeScript 3.3 的时候,support for calling unions of methods/functions 就开始多了,所以下面的问题及其答案已经过时了一段时间。


TypeScript 3.2 或更低版本的原始答案:

正如我在 cmets 中提到的,you can't call methods which are union types(classA[] | classB[])['map']的调用签名是

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

编译器放弃了。您可以做的是将您的类型从(classA[] | classB[]) 扩大到(classA | classB)[]。前者是“要么这是所有classA元素的数组,要么是所有classB元素的数组”,而后者是“这是一个元素数组,每个元素要么是classA要么classB"。前者更具体,因为如果您知道arr[0]classA,那么arr[1] 也将是classA... 而后者不太具体,因为arr[0] 可能是classAarr[1] 可能是classB。后者的好处是(classA | classB)[]['map'] 具有单一签名:

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

可以调用它。


您的下一个问题“如果我在任何类中定义任何内容,为什么它会停止工作”与structural typing 有关。简而言之,如果 classAclassB 具有相同的成员,TypeScript 认为它们是相同的类型。这可能令人惊讶,因为许多其他类型语言使用nominal typing,其中具有不同名称的两种类型必然是不同的类型。但是 TypeScript 并不是这样工作的。

如果classAclassB 都没有属性,它们将被视为等同于{},即空类型。然后(classA[])|(classB[]) 减少为({}[])|({}[]),也就是({}[])。这不是联合,所以你可以调用它的map 方法。

如果您希望 classA 被编译器视为与 classB 不同,那么您应该 give them different properties,至少在(并且除非)TypeScript 获得更多一流的名义类型之前。


希望对您有所帮助。祝你好运。

【讨论】:

    猜你喜欢
    • 2017-12-19
    • 2017-07-14
    • 2018-11-12
    • 2020-02-07
    • 1970-01-01
    • 2018-05-09
    • 2023-03-20
    • 2017-09-01
    • 2021-02-06
    相关资源
    最近更新 更多