3

我有一个从对象获取属性的函数。

// Utils.ts
export function getProperty<T, K extends keyof T>(obj: T, key: string): T[K] {
    if (key in obj) { return obj[key as K]; }
    throw new Error(`Invalid object member "${key}"`);
}

我想检查返回的属性是否是具有给定签名的函数,然后使用提供的参数调用该属性。

getProperty()用于动态获取对象的方法之一并调用它。我试过了:

let property: this[keyof this] = utils.getProperty(this, name);
if (typeof property === 'function') ) { property(conf); }

但这给出了“无法调用类型缺少调用签名的表达式。类型'any'没有兼容的调用签名。” 错误。我知道来自的属性getProperty()确实可以是任何类型,但是如何确定它是带有(conf: {}): void签名的函数?

4

2 回答 2

5

看起来函数类型不存在类型typeof保护。这似乎是设计使然;请参阅microsoft/TypeScript#2072(该问题与instanceof类型保护有关,但我猜这是类似的推理)。

但是,TypeScript 确实具有用户定义的类型保护,这意味着您可以编写一个函数来以任何您喜欢的方式缩小其参数的类型。


当代码编译为 JavaScript 时,TypeScript 的静态类型系统被删除。在运行时,没有(conf: {}) => void可以检查的东西。如果你想编写一个运行时测试来区分类型(conf: {}) => void的值和其他类型的值,你只能做到这一点。

你可以测试如果typeof x === "function". 但这只是告诉你它是一个函数。它没有指出函数需要多少参数以及它们的类型。您还可以检查x.length === 1该函数是否需要一个参数,尽管在Function.length涉及其余参数和默认参数时有一些注意事项。但现在你有点卡住了。

在运行时,任何有关函数参数类型的信息都将被删除,如果这些函数甚至最初来自 TypeScript 代码。最多您可以通过使用一些测试参数调用该函数并检查是否有问题来“探测”该函数。但这是一个具有可能副作用的破坏性测试,并且违背了在调用函数之前检查函数类型是否正确的目的。


也许你对这个限制很好,只是假设一个函数lengthis1是一个足够好的测试。或者您可能有其他一些测试(例如,您可以添加一个名为的属性isOfRightType,其值是true您关心的所有此类函数,然后只测试该属性。这通过引入误报的可能性来消除误报)。一旦你知道了你的运行时测试,你就可以做一个类型保护:

function isFunctionOfRightType(x: any): x is (conf: {})=>void {
   return (typeof x === 'function') && (x.length === 1); // or whatever test
}

// ... later

let property: this[keyof this] = utils.getProperty(this, name);
if (isFunctionOfRightType(property)) { property(conf); } // no error now

Playground 代码链接

于 2018-05-16T13:07:29.460 回答
1

在你的函数签名中有一个错误,key参数应该是类型K,当你使用常量作为参数时,这会给你更好的推断:

export function getProperty<T, K extends keyof T>(obj: T, key: string): T[K] {
    if (key in obj) { return obj[key as K]; }
    throw new Error(`Invalid object member "${key}"`);
}

let foo = {
    fn (conf: {}): void {}
}
let fn = getProperty(foo, "fn");
fn({}); // callable 

当您使用一个字符串且在编译时未验证的密钥时,问题是编译器无法真正帮助您解决任何问题。它将假设由于您索引我的任意字符串,因此返回类型可以是目标的任何有效字段类型。无法在运行时验证函数参数类型,因为它们将被擦除,但您可以验证参数计数:

let property:  Function = getProperty(this, name) as any;
if (typeof property === 'function' && property.length == 1)  
{ 
    property({}); 
}
于 2018-05-16T13:04:17.797 回答