[英]Generics in nested TypeScript function
I want to write a TypeScript function that accepts only certain strings, which are the property names of a specified type, but these properties must have a type of string
, number
or Date
.我想编写一个只接受某些字符串的 TypeScript 函数,这些字符串是指定类型的属性名称,但这些属性的类型必须是
string
、 number
或Date
。 This function returns another function that accepts the original object (of which the property names were selected) and returns the specified property (actually it does something else, but this is the simplest case that produces the same problem)此函数返回另一个函数,该函数接受原始对象(其中选择了属性名称)并返回指定的属性(实际上它做了其他事情,但这是产生相同问题的最简单的情况)
I tried it like this我这样试过
"use strict";
type InstancePropertyNames<T, I> = {
[K in keyof T]: T[K] extends I ? K : never;
}[keyof T];
type StringPropertyNames<T> = InstancePropertyNames<T, string>;
type NumberPropertyNames<T> = InstancePropertyNames<T, number>;
function doSomething<T extends object>(
key:
| StringPropertyNames<T>
| NumberPropertyNames<T>
| InstancePropertyNames<T, Date>
) {
return function(obj: T) {
const res = obj[key];
return res;
}
}
function doSomethingEasy<T extends object>(
obj: T,
key:
| StringPropertyNames<T>
| NumberPropertyNames<T>
| InstancePropertyNames<T, Date>
) {
const res = obj[key];
return res;
}
type Test = {
asdf: string;
foo: number;
bar: boolean;
qux: Date;
};
const test: Test = {
asdf: "asdf",
foo: 42,
bar: true,
qux: new Date()
}
const res = doSomething<Test>("asdf")(test);
console.log(res);
const resEasy = doSomethingEasy(test, "asdf");
console.log(resEasy);
The problem is now that the type of res
inside the nested function is something complex (instead of simply number | string | Date
), also in doSomethingEasy
.现在的问题是嵌套函数内的
res
类型是复杂的(而不是简单的number | string | Date
),也在doSomethingEasy
。 Is there any way to simplify the type of the property in the nested function to the primitive types?有什么方法可以将嵌套函数中的属性类型简化为原始类型?
Your doSomething()
is a curried function where you don't know the type that T
should be until you call the function returned by doSomething()
.你的
doSomething()
是一个柯里化的函数,在你调用doSomething()
返回的函数之前,你不知道T
应该是什么类型。 This makes it nearly impossible for the compiler to infer T
.这使得编译器几乎不可能推断
T
。 To make this obvious, imagine this:为了使这一点显而易见,想象一下:
const f = doSomething("asdf"); // what type should f be?
f({asdf: 123});
f({asdf: "a"});
f(test);
The parameter T
gets inferred by the compiler when you call doSomething("asdf")
to produce f
.当您调用
doSomething("asdf")
以生成f
时,编译器会推断参数T
。 But what should it be inferred to be?但应该推断出它是什么? Should it depend on whatever you happen to call
f()
with?它应该取决于你碰巧用什么来调用
f()
吗? And what if you call f()
with different types like I did above?如果你像我上面那样用不同的类型调用
f()
怎么办? The only thing that would work here is if f
is itself a generic function, but the compiler does not know enough to try to infer that.唯一可以在这里工作的是,如果
f
本身是一个泛型函数,但编译器没有足够的知识来尝试推断它。
So you get T
inferred as something like {asdf: any}
(because reverse inference through conditional types like InstancePropertyNames
is also essentially impossible), and then f()
spits out any
whatever you call it with.因此,您将
T
推断为类似{asdf: any}
(因为通过条件类型(如InstancePropertyNames
)进行反向推理基本上也是不可能的),然后f()
吐出您调用它的any
内容。 Oops.哎呀。
Instead what I'd do here is try to write your signature so that at each step the compiler only needs to know the type a function's parameters in order to infer its type parameters.相反,我在这里要做的是尝试编写您的签名,以便在每一步编译器只需要知道函数参数的类型即可推断其类型参数。 Something like this:
像这样的东西:
function doSomething<K extends PropertyKey>(
key: K
) {
return function <T extends Record<K, string | number | Date>>(obj: T) {
const res = obj[key];
return res;
}
}
Here doSomething
will accept a key
of type K
which can be any key-like value.这里
doSomething
将接受K
类型的key
,它可以是任何类似键的值。 Since key
determines K
, you can assume that K
will be inferred properly.由于
key
决定K
,您可以假设K
将被正确推断。 We don't make doSomething()
generic in T
since nothing passed to doSomething()
will help determine what T
should be.我们不会在
T
中使doSomething()
泛型,因为没有传递给doSomething()
将有助于确定T
应该是什么。
The return value of doSomething()
is another generic function which accepts an obj
of type T
which is constrained to Record<K, string | number | Date>
doSomething()
的返回值是另一个通用函数,它接受类型为T
的obj
,该obj
约束为Record<K, string | number | Date>
Record<K, string | number | Date>
Record<K, string | number | Date>
. Record<K, string | number | Date>
. So we pushed the T
generic into the signature for the return function, which should actually know enough to infer it.所以我们将
T
泛型推入返回函数的签名中,它实际上应该知道足够的信息来推断它。
(This returned function doesn't have to be generic in T
; you could have type obj
as Record<K, string | number | Date>
directly, but then excess property checks kick in where you might not want them, and also the return value of the returned function would always be string | number | Date
which is wider than you might want. Benefits of keeping T
around below:) (这个返回的函数不必是通用在
T
,你可以有键入obj
作为Record<K, string | number | Date>
直接,但随后多余的财产检查踢在你可能不希望他们,也回报返回函数的值将始终是string | number | Date
,它比您想要的更宽。将T
保留在下面的好处:)
Since K
is already known, this should mean that T
will be inferred as the type of obj
and it will only accept obj
parameters whose value at the K
key are assignable to string
, number
, or Date
.由于
K
是已知的,这意味着T
将被推断为obj
的类型,并且它只接受obj
参数,其K
键的值可分配给string
、 number
或Date
。 And the return type of the returned function will be T[K]
, the type of the property of T
at key K
.并且返回函数的返回类型将为
T[K]
,即T
在键K
处的属性类型。
Let's see if it works:让我们看看它是否有效:
const f = doSomething("asdf");
// const f: <T extends Record<"asdf", string | number | Date>>(obj: T) => T["asdf"]
const res = doSomething("asdf")(test); // string
console.log(res); // "asdf"
console.log(
doSomething("bar")({foo: true, bar: new Date(), baz: 123}).getFullYear()
); // 2020
Looks good.看起来挺好的。 The call to
doSomething("asdf")
returns a generic function accepting values assignable to {asdf: string | number | Date}
对
doSomething("asdf")
的调用返回一个通用函数,该函数接受可分配给{asdf: string | number | Date}
值{asdf: string | number | Date}
{asdf: string | number | Date}
{asdf: string | number | Date}
. {asdf: string | number | Date}
。 And so doSomething("asdf")(test)
produces a value of type string
.所以
doSomething("asdf")(test)
产生一个string
类型的值。 The call at the end is shows the versatility of this implementation;最后的调用展示了这个实现的多功能性; you get a
Date
out because the compiler knows that the bar
property of the passed-in object literal is a Date
.你得到一个
Date
,因为编译器知道传入的对象文字的bar
属性是一个Date
。 (That's why having both K
and T
be generic is useful. Otherwise Record<K, string | number | date>[K]
is just string | number | date
. The type T[K]
is narrower and probably more helpful to you.) (这就是为什么
K
和T
都是通用的很有用。否则Record<K, string | number | date>[K]
只是string | number | date
。类型T[K]
更窄,可能对你更有帮助。 )
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.