不列举所有可能性的打字稿推理 [英] typescript inference without enumerating all possibilities

查看:31
本文介绍了不列举所有可能性的打字稿推理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道以下问题是否有解决方案:我有两个接受不同输入参数的函数,我有一个对象将每个函数映射到一个字符串.

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/caseif/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 最终采用 f1f2 的类型并将它们模糊成一个接受和生成 字符串 |号码.我们使用类型断言 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆