在需要功能的地方,打字稿允许我传递带有不兼容的`apply`属性的对象

时间:2019-04-26 21:53:43

标签: typescript-typings

当前在启用strict模式的情况下使用打字稿 3.4.5 ...

背景故事

不幸的是,我只是遇到打字稿无法保护我免受自己的错误困扰的情况。我试图弄清楚为什么打字稿无法捕获此错误。

我正在为这样的函数编写类型声明:

function acceptVisitors (visitor) {
    visitor.apply(value);
}

敏锐的观察者可能会指出,visitor的类型可以通过以下两种方式之一进行定义-作为函数或具有apply属性的对象:

type visitorType = (this: IValue) => void;
// or
type visitorType = {
    apply: (value: IValue) => void;
};

事实证明,就我而言,是后者。添加类型声明后,我继续编写以下错误代码:

// This is incorrect because it doesn't pass it as an argument.
// Rather, the `this` context is set to the value.
acceptVisitors((value: IValue) => { ... });

现在,令人困惑的是,当我传递一个类型与visitorType不兼容的函数时,Typescript没有显示错误。

简化示例

让我们将参数类型更改为字符串,然后逐步遍历。
我正在定义一个名为func的类型,该类型需要一个字符串参数。

type func = (param1: string) => void;

函数本质上是可调用对象,也具有apply方法。

declare let f: func;
f.apply(undefined, ['str']);
// all good

现在这是另一种类型-具有apply属性的对象。

type objectWithApplyProp = {
    apply: (param1: string) => void;
};

我们可以调用apply属性,但不能以相同的方式...

declare let o: objectWithApplyProp;
o.apply(undefined, ['str']); // Error: Expected 1 arguments, but got 2.

并且objectWithApplyProp的呼叫签名不适用于func

o.apply('str'); // ok
f.apply('str'); // Error: The 'this' context of type 'func' is not assignable to 
                // method's 'this' of type '(this: string) => void'

进一步的测试表明f是可分配给o的,但不是相反的,这是有道理的……所有函数都是对象,但并非所有对象都是可调用的。

但是为什么f被认为可分配给oobjectWithApplyProp的类型需要一个与某些类型匹配的apply值,而func与它不匹配

应该从函数的参数中推断出函数的apply签名,但是打字稿似乎并不能推断出它。

因此,欢迎您提供任何反馈意见。我错了吗,或者打字稿有限制吗?这是一个已知问题吗?谢谢

1 个答案:

答案 0 :(得分:0)

这是发生这种情况的技术原因,也是一种解决方法:

Typescript的内置lib/es5.d.ts声明文件定义了Function.apply,其参数类型为any。还将Function.prototype定义为“任意”。

interface Function {
    apply(this: Function, thisArg: any, argArray?: any): any;
    call(this: Function, thisArg: any, ...argArray: any[]): any;
    bind(this: Function, thisArg: any, ...argArray: any[]): any;
    toString(): string;
    prototype: any;
    readonly length: number;
    // Non-standard extensions
    arguments: any;
    caller: Function;
}

我想默认情况下所有函数表达式的类型都为Function

因此,由于该函数没有基于内置apply的强类型apply方法,因此可以将该函数分配给具有不兼容Function属性的对象类型。因此,打字稿无法确定apply签名是否不同。

Typescript 3.2引入了CallableFunction,其apply声明中具有通用参数。但是我还没有弄清楚如何解决这个问题。

一种解决方法是定义一个更强大的函数类型,并将其手动分配给该函数。解决方法有点繁琐,但可以。

interface func extends Function {
    (param1: string): void;
    // manually define `apply
    apply<T, R> (thisArg: T, args: [string]): R;
}
interface objectWithApplyProp { // unchanged
    apply: (param1: string) => void;
}
// Now we have proper errors here:
o = f;
// Type 'func' is not assignable to type 'objectWithApplyProp'.
//  Types of property 'apply' are incompatible.
//    Type '<T, R>(thisArg: T, args: [string]) => R' is not assignable to type '(param1: string) => void'. ts(2322)
f = o;
// Type 'objectWithApplyProp' is missing the following properties from type 'func': call, bind, prototype, length, and 2 more. ts(2740)