简体   繁体   English

使用泛型和keyof时推断回调参数的属性类型

[英]Deduce property type of callback argument when using generics and keyof

I'm struggling to write a code that will deduce the type of args.value inside if scope:我正在努力编写一个代码来推断if范围内的args.value类型:

class Foo {
    public id: number;
    public name: string;
    public birth: Date;
}

interface ISetEventArgs<T> {
    field: keyof T;
    value: T[keyof T];
}

function bind<T>(obj: T, event: "set", handler: (args: ISetEventArgs<T>) => void): void {
    // Void
}

let f: Foo = new Foo();

bind<Foo>(f, "set", (args: IArgs<Foo>): void => {
    if (args.field === "id") {
        let id: number = args.value; // Error: Type 'string | number | Date' is not assignable to type 'number'.
    }
    else if (args.field === "name") {
        // ...
    }
    else if (args.field === "birth") {
        // ...
    }
});

I tried to solve this situation by writing something like this, but It does not feel right:我试图通过写这样的东西来解决这种情况,但感觉不对:

function getValue<T, K extends keyof T>(value: T[keyof T], key: K): T[K] {
    return value;
}

// Usage:
if (args.field === "id") {
    let id: number = getValue<Foo, "id">(args.value, args.field); // Correct type.
    // Can also be used as: getValue<Foo, "id">(args.value, "id");
}

Any ideas?有任何想法吗? Even if the solution requires using helper function I would really like to be able to use it in a more clean way, such as (if possible) getValue<Foo, "id">(args.value) or getValue(args.value, args.field)即使解决方案需要使用辅助函数,我真的希望能够以更干净的方式使用它,例如(如果可能) getValue<Foo, "id">(args.value)getValue(args.value, args.field)

I don't think it can be done without a helper function - typescript type inference is not taking into account that types for field and value are interdependent.我认为没有辅助函数就无法完成 - 打字稿类型推断没有考虑到fieldvalue类型是相互依赖的。

So you have to use so-called user-defined type guard function to express type relationship explicitly:所以你必须使用所谓的用户自定义类型保护函数来显式表达类型关系:

class Foo {
    public id: number;
    public name: string;
    public birth: Date;
}

interface ISetEventArgs<T> {
    field: keyof T;
    value: T[keyof T];
}

function bind<T>(obj: T, event: "set", handler: (args: ISetEventArgs<T>) => void): void {
    // Void
}

let f: Foo = new Foo();

// type guard
function argsForField<T, F extends keyof T>(args: ISetEventArgs<T>, field: F):
         args is { field: F; value: T[F]} {
    return args.field === field;
}

bind<Foo>(f, "set", (args: ISetEventArgs<Foo>): void => {
    if (argsForField(args, "id")) {
        let id: number = args.value; //no error
    }
    else if (argsForField(args, "name")) {
        let name: string = args.value
    }
    else if (argsForField(args, "birth")) {
        let birth: Date = args.value;
    }
});

This question has been bugging me all day, so I played around with it, and while I don't have a solution (sadly), I did discover some interesting behavior which may or may not be helpful to you, depending on your exact use case.这个问题一整天都在困扰着我,所以我玩弄了它,虽然我没有解决方案(可悲的是),但我确实发现了一些有趣的行为,这些行为可能对您有帮助,也可能没有帮助,具体取决于您的确切用途案件。

TL;DR: You can get what you want in the specific case of Foo, but not in general. TL;DR:你可以在 Foo 的特定情况下得到你想要的东西,但不是一般的。 It seems to be a limitation on typescript's part.这似乎是打字稿部分的限制。

So first, lets bind the field and value of ISetEventArgs together:首先,让我们将ISetEventArgs的字段和值ISetEventArgs在一起:

interface ISetEventArgs<T, K extends keyof T> {
    field: K;
    value: T[K];
}

Now, the problem is the type:现在,问题是类型:

ISetEventArgs<Foo, keyof Foo>

resolves to:决定:

ISetEventArgs<Foo, "id"|"name|"birth">

but we want it to be:但我们希望它是:

ISetEventArgs<Foo, "id"> | ISetEventArgs<Foo, "name"> | ISetEventArgs<Foo, "birth">

Since in the second case we can take advantage of typescript's discriminated unions capability.因为在第二种情况下,我们可以利用 typescript 的可区分联合功能。 These seem to me to be semantically the same, but typescript will only narrow the second case.在我看来,这些在语义上是相同的,但 typescript 只会缩小第二种情况。 So we need to do some type shenanigans to get it into that form.所以我们需要做一些恶作剧来让它变成那种形式。

So, if we define a type:所以,如果我们定义一个类型:

type FooArgs = {[K in keyof Foo]: ISetEventArgs<Foo, K>}[keyof Foo]

and that resolves to what we want... But sadly, if we try to extend this pattern so it works with any type:这解决了我们想要的......但遗憾的是,如果我们尝试扩展这种模式,使其适用于任何类型:

type GenericArgs<T> = {[K in keyof T]: ISetEventArgs<T, K>}[keyof T];
type GenricFooArgs = GenericArgs<Foo>;

Suddenly GenericFooArgs resolves to the first type above, instead of the second?!突然GenericFooArgs解析为上面的第一种类型,而不是第二种?! I don't know why declaring FooArgs manually comes out differently than using GenericArgs<Foo> .我不知道为什么手动声明FooArgs与使用GenericArgs<Foo>

So, if you use FooArgs in place of ISetEventArgs<T> , you'll get what you want when you implement your handler.因此,如果您使用FooArgs代替ISetEventArgs<T> ,那么您将在实现处理程序时获得所需内容。 But... You've lost the generic capability of bind , so it may not be a worthy trade after all.但是......你已经失去了bind的通用能力,所以它可能根本不是一个值得的交易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM