[英]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.我认为没有辅助函数就无法完成 - 打字稿类型推断没有考虑到
field
和value
类型是相互依赖的。
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.