当表达式用作 属性 名称以使用类型具有可选字段的参数访问函数时,TypeScript 无法编译。 TS2349

TypeScript not compiling when expression used as property name to acess function with an argument who's type has optional fields. TS2349

以下代码无法在 TypeScript 2.1.4 中编译并给出错误:

错误

Error:(6, 31) TS2349:Cannot invoke an expression whose type lacks a call signature. Type '((args: string[], action: A) => string[]) | ((args: string[], action: C) => string[])' has no compatible call signatures.

代码

/*
* Set up a function to take some arguments
* and an action and use the map to run the appropriate
* function bases on the type property of the action 
*/ 

const caller = (args: string[] = [], action): string[] => {
        return map[action.type] ? map[action.type](args, action) : args;
 };

interface Action {
    type: any; 
}

const TYPE_A = "type_a";

interface A extends Action {
    from: number;
    to: number;
    id?: number; // optional parameters causing the issue.
    prop1?: number;
}

const TYPE_B = "type_b";

interface B extends Action {
    from: number;
    to: number;
}

const TYPE_C = "type_c";

interface C extends Action {
    id: number;
    prop1: number;
}

const map = {
    [TYPE_A]: (args: string[], action: A) => {
        return ["a"];
    },
    [TYPE_B]: (args: string[], action: B) => {
        return ["b"];
    },
    [TYPE_C]: (args: string[], action: C) => {
        return ["c"];
    }
};

caller([], {type: TYPE_A, from: 2, to: 1});

动机

我在映射中使用表达式作为 属性 的动机是这样我可以更改 属性 常量的值而无需重构映射。

解决方案

有两种解决方法:

a)删除interface A中的可选字段。

interface A extends Action {
    from: number;
    to: number;
    id: number; // optional parameters causing the issue not optional.
    prop1: number;
}

b) 将地图属性声明更改为值而不是表达式,并保留可选字段。

const map = {
    "type_a" : (args: string[], action: A) => {
        return ["a"];
    },
    "type_b": (args: string[], action: B) => {
        return ["b"];
    },
    "type_c": (args: string[], action: C) => {
        return ["c"];
    }
};

问题

我的问题是为什么首先显示错误,有人可以给我解释一下吗?

原因是AC不兼容,因为prop1A中是可选的,在C中是必需的。因此,您不能在需要采用 A 的函数的地方使用采用 C 的函数:

let fa: (a: A) => void;
let fc: (c: C) => void;

fa = fc;
fc = fa;

错误:

test.ts(49,1): error TS2322: Type '(c: C) => void' is not assignable to type '(a: A) => void'.
  Types of parameters 'c' and 'a' are incompatible.
    Type 'A' is not assignable to type 'C'.
      Property 'id' is optional in type 'A' but required in type 'C'.
test.ts(50,1): error TS2322: Type '(a: A) => void' is not assignable to type '(c: C) => void'.
  Types of parameters 'a' and 'c' are incompatible.
    Type 'C' is not assignable to type 'A'.
      Property 'from' is missing in type 'C'.

当你用文字 属性 名称声明一个映射时,类型推断可以在你做 caller([], {type: TYPE_A, from: 2, to: 1}); 时计算出来,你实际上是在使用 "type_a" 键访问一个值,所以它知道该函数参数类型正好是 A。当使用计算键声明 map 时,它不能这样做,可能是因为它只是在编译时不计算键的表达式,所以它推断 map 值的联合类型,并且联合的两个成员彼此不兼容,因为AC 不兼容。

您也可以通过明确声明类型 map:

来解决这个问题
const map: {[key: string]: (args:string[], action: Action) => string[]} = {
    [TYPE_A]: (args: string[], action: A) => {
        return ["a"];
    },
    [TYPE_B]: (args: string[], action: B) => {
        return ["b"];
    },
    [TYPE_C]: (args: string[], action: C) => {
        return ["c"];
    }
};

也有效。