根据其他字段推断接口字段的类型

时间:2020-05-13 20:49:20

标签: reactjs typescript typescript-generics

我正在定义一个以接口为参数的泛型函数。
此接口具有多个字段,其中一个字段是通用参数的键。
另一个字段也采用了此通用参数的键,但是我想强制两者相等,而无需用户明确指定它们。

这是一个具体的最小示例:

// Generic interface representing the parameter of the generic function
interface Inte<DataType extends object, K extends keyof DataType = keyof DataType> {
    key: K;
    fn: (val: DataType[K]) => void;
}

// Define the generic function
function test<DataType extends object>(arg: Inte<DataType>);

// Testing interface
interface Base {
    first: string;
    second: number;
}

// Testing function call
test<Base>({
    key: "first",
    fn: (val: number) => {}, // Error on 'fn', here
});

以上代码在fn的定义行报告了一个错误,指出参数类型(number)无法分配给string,因为它确实推断出了我的第二个参数通用接口K仍然是string | number,尽管key应该可以帮助它找到正确的类型。

整个错误是(不知道为什么在其中放置一个随机的ReactText。也许是因为我在.tsx文件中尝试了这个错误?):

Type '(val: number) => void' is not assignable to type '(val: ReactText) => void'.
  Types of parameters 'val' and 'val' are incompatible.
    Type 'ReactText' is not assignable to type 'number'.
      Type 'string' is not assignable to type 'number'.ts(2322)

我终将收到一条错误消息,指出参数必须为string而不是number。 (如果我更改为val: string,也完全没有错误。

这个问题有解决方案吗?我知道我可以做"strictFunctionTypes": false,但是那会削弱我在那里的类型检查。

我很确定我可以做些可靠的事情来解决这个问题!

顺便说一句,如果它可能以任何方式相关,那么实际代码发生在React使用的上下文中,这意味着我不能更改通用函数的参数,因为它是一个组件。

1 个答案:

答案 0 :(得分:0)

问题出在您对Inte的定义中。您给K设置了默认值keyof DataType,并且没有在function test<DataType extends object>(arg: Inte<DataType>)中指定固定值,这意味着它将/ always /默认为keyof DataType,即使如果您在牢记key的情况下调用该函数。解决此问题的一种方法是使test需要K的类型:

declare function test<DataType extends object, K extends keyof DataType>(arg: Inte<DataType, K>);

// Testing function call
test<Base, "first">({
    key: "first",
    fn: (val: number) => { },
    /*
    ERR on fn:
    Type '(val: number) => void' is not assignable to type '(val: string) => void'.
      Types of parameters 'val' and 'val' are incompatible.
        Type 'string' is not assignable to type 'number'
    */
});

现在,如果您编写(val: string),它将可以正常工作。显然这是不符合人体工程学的,因为您需要两次写下“ first”。不过,很难避免这种情况,因为DataType已经是一种浮点型参数,例如它更多是一种约束,而不是可以从输入中得出的东西。不幸的是,在TS中,一旦指定了一个通用参数,就必须指定它们的 all

编辑:部分参数推断的一种常见解决方法是使用咖喱函数:

function test<DataType extends object>() {
    return function innerTest<K extends keyof DataType>(arg: Inte<DataType, K>) {
        throw new Error("Not yet implemented");
    }
};

test<Base>()({
    key: "first",
    fn: (val: number) => { },
    // still an err, but changing to `val: string` makes it work
});

现在test<DataType>()实际上创建了一个约束为DataType的测试器函数。返回的函数是对K的推断。