不列举所有可能性的打字稿推理 [英] typescript inference without enumerating all possibilities
问题描述
我想知道以下问题是否有解决方案:我有两个接受不同输入参数的函数,我有一个对象将每个函数映射到一个字符串.
I would like to know if there is a solution for the following problem : I have two functions that accept different input parameters, I have an object mapping each function to a string.
我定义了一个类型,它是一个联合类型的对象,包含映射中一个函数的键和一个与函数参数类型匹配的值.给定一个具有该类型的对象,我想从地图对象中检索相应的函数并使用该值调用它,但 typescript 不进行类型推断.
I define a type that is a union type of object containing the key of one function in the map and a value matching the function parameters type. Given an object with that type, I would like to retrieve the corresponding function from the map object and call it with the value but typescript does not do the type inference.
const f1 = (value: number) => value;
const f2 = (value: string) => value;
const map = {
f1,
f2
};
type MyType = {
fun: "f1";
value: Parameters<typeof map["f1"]>;
} | {
fun: "f2";
value: Parameters<typeof map["f2"]>;
};
// does not work
const test1 = (b: MyType) => {
map[b.fun].apply(null, b.value);
};
// works
const test2 = (b: MyType) => {
if (b.fun === "f1") {
map[b.fun].apply(null, b.value);
} else {
map[b.fun].apply(null, b.value);
}
};
是否可以在无需枚举 switch 或 if/else 中的所有可能性的情况下执行此类操作?
Is it possible to do something like this without having to enumerate all possibilities in a switch or if/else ?
推荐答案
您已经遇到了 TypeScript 对我一直称之为 相关记录类型.现在它看到 b.fun
是联合类型 "f1" |"f2"
,并且 b.value
是联合类型 [string] |[数字]
,并将它们视为不相关.通常 一个函数的联合只能用它的参数的交集来调用,在这种情况下是 [string] &[号码]
.由于 [string] |[number]
不可分配给 [string] &[number]
,编译器抱怨.事实上,没有任何值可以分配给 [string] &[number]
,因为它的第一个元素需要是一个 string &number
相当于 never
.
You've run into TypeScript's less-than-stellar support for what I've been calling correlated record types. Right now it sees that b.fun
is a union type "f1" | "f2"
, and that b.value
is a union type [string] | [number]
, and it treats these as uncorrelated. Normally a union of functions can only be called with an intersection of its parameters, which in this case would be [string] & [number]
. Since [string] | [number]
is not assignable to [string] & [number]
, the compiler complains. In fact no value is assignable to [string] & [number]
, since its first element would need to be a string & number
which is equivalent to never
.
为了让编译器验证 map[b.fun].apply(null, b.value)
是类型安全的,它本质上需要多次分析代码,一次为b
的每种可能类型.您可以通过复制 switch
/case
或 if
/else
语句中的代码来明确地实现这一点,但是它不会自动发生(这是有道理的,因为随着代码中联合类型值数量的增加,它会导致编译时间呈指数增长),并且 您甚至不能要求编译器在选择加入的基础上执行此操作.
In order for the compiler to verify that map[b.fun].apply(null, b.value)
is type safe, it would essentially need to analyze the code multiple times, one for each possible type of b
. You can make this happen explicitly by duplicating the code in a switch
/case
or if
/else
statement, but it doesn't happen automatically (which makes sense because it would lead to exponentially increasing compile times as the number of union-typed values in code increases), and you can't even ask the compiler to do it on an opt-in basis.
目前,唯一合理的解决方法是接受您比编译器更了解代码的类型安全,并使用 type assertion 或等价物告诉编译器不要担心它,像这样:
For now, the only reasonable workaround is to accept that you know more than the compiler about the type safety of the code, and to use a type assertion or the equivalent to tell the compiler not to worry about it, like this:
type MapVals = typeof map[keyof typeof map];
type LooselyTypedMapFunction = (...x: Parameters<MapVals>) => ReturnType<MapVals>
const test1 = (b: MyType) => {
(map[b.fun] as LooselyTypedMapFunction).apply(null, b.value);
};
这里的类型 LooseLyTypedMapFunction
最终采用 f1
和 f2
的类型并将它们模糊成一个接受和生成 字符串 |号码
.我们使用类型断言 map[b.fun] as LooselyTypedMapFunction
来欺骗编译器.我们告诉它 map[b.fun]
将接受一个 string
并且它也将接受一个 number
.当然,这是错误的,但只要您传入相关的 b.value
列表,而不是诸如 `[Math.random()<0.5 之类的随机内容,就不会遇到问题?哎呀":123]).
Here the type LooseLyTypedMapFunction
ends up taking the types of f1
and f2
and blurring them into a single function that accepts and produces string | number
. And we use the type assertion map[b.fun] as LooselyTypedMapFunction
to lie a little bit to the compiler. We're telling it that map[b.fun]
will accept a string
and it will also accept a number
. This is false, of course, but will not run into a problem as long as you pass in the correlated b.value
list and not some random thing like `[Math.random()<0.5 ? "oops" : 123]).
该类型断言可能比您想做的工作多,但至少如果您将任何太疯狂的东西传递给 map[b.fun]
,它至少可能会被捕获,因为 [string]|[number]
至少会禁止 [{foo: string}]
.你可以使用这样一个不太安全的断言:
That type assertion might be more work than you want to do, but at least it might catch if you passed anything too crazy into map[b.fun]
, since [string] | [number]
will at least prohibit [{foo: string}]
. You can use a less safe assertion like this:
const test2 = (b: MyType) => {
(map[b.fun] as Function).apply(null, b.value);
};
不需要对 LooselyTypedMapFunction
进行类型处理,但也接受 (map[b.fun] as Function).apply(null, [{foo: 123}]);代码>.所以要小心.
which doesn't require type juggling of a LooselyTypedMapFunction
but will also accept (map[b.fun] as Function).apply(null, [{foo: 123}]);
. So be careful.
好的,希望能给你一些指导.祝你好运!
Okay, hope that gives you some direction. Good luck!
这篇关于不列举所有可能性的打字稿推理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!