简体   繁体   English

使用 Typescript 从开关盒返回的类型推断

[英]Type inference from switch case return with Typescript

type of the functions函数类型

type fn01 = (name: string) => void
type fn02 = (age: string) => void
type fn03 = (description: number) => void

I have this case:我有这种情况:

type Options = 'op1' | 'op2' | 'op3'

const test = (options) => {
    switch(options) {
          case 'op1':
            return fn01
          case 'op2':
            return fn02
          case 'op3':
            return fn03
          default
            return null;
        }
    }

Function Usage: Function 用途:

const chosenFN = test('op1');

choseFN()

When using the chosenFN it should show the type from the function that would be fall into the switch case, but instead shows a Intersection of all of them.当使用 selectedFN 时,它应该显示来自 function 的类型,该类型将落入开关盒中,而是显示所有它们的交集。

What am I missing?我错过了什么? I thought that since the function that's used is inside of a switch case, it would infer the correct type based on the argument passed.我认为由于使用的 function 位于开关盒内,因此它会根据传递的参数推断出正确的类型。

Thanks, Renan谢谢,雷南

In order to achieve that, you need to use function overloads - https://www.typescriptlang.org/docs/handbook/functions.html#overloads :为了实现这一点,您需要使用 function 重载 - https://www.typescriptlang.org/docs/handbook/functions.html#overloads

type Options = 'op1' | 'op2' | 'op3'
type fn01 = (name: string) => void
type fn02 = (age: string) => void
type fn03 = (description: number) => void

// here overloads - define output for every input
function test(a: 'op1'): fn01;
function test(a: 'op2'): fn02;
function test(a: 'op3'): fn03;

function test(options: Options) {
    switch(options) {
      case 'op1':
        return (name: string) => {}
      case 'op2':
        return (age: string) => {}
      case 'op3':
        return (description: number) => {}
      default:
        throw new Error('no such value')  
      }
}

const f = test('op1') // it is fn01

The compiler does not generally infer function return types that depend on which specific value was input into it.编译器通常不会根据输入的特定值推断 function 返回类型。 Control flow type analysis acts to narrow the types of concrete-typed variables inside the function implementation, and so the switch statement serves to know that options is exactly op2 (for example) inside the relevant case block, but control flow analysis doesn't do much to the return type of the function. 控制流类型分析的作用是缩小 function 实现中具体类型变量的类型,因此switch语句用于知道相关case块中的options恰好是op2 (例如),但控制流分析不起作用与 function 的返回类型非常相似。

Generally the inferred return type of a function will be the union of all the types that are return ed from the function, irrespective of control flow analysis.通常,function 的推断返回类型将是从 function return的所有类型的并集,与控制流分析无关。 That means the signature of test() is inferred as something like function test(options: Options): fn01 | fn02 | fn03 | null这意味着test()的签名被推断为类似于function test(options: Options): fn01 | fn02 | fn03 | null function test(options: Options): fn01 | fn02 | fn03 | null function test(options: Options): fn01 | fn02 | fn03 | null . function test(options: Options): fn01 | fn02 | fn03 | null

When you go to actually call a function of type fn01 | fn02 | fn03 | null当您 go 实际调用 fn01 类型的fn01 | fn02 | fn03 | null fn01 | fn02 | fn03 | null fn01 | fn02 | fn03 | null , you will run into trouble. fn01 | fn02 | fn03 | null ,你会遇到麻烦。 With the --strictNullChecks compiler option on, you can't call it at all (and you probably should use the --strict compiler options, since they catch bugs).启用--strictNullChecks编译器选项后,您根本无法调用它(您可能应该使用--strict编译器选项,因为它们会捕获错误)。

Assuming you have a function of type fn01 | fn02 | fn03假设你有一个 fn01 类型的fn01 | fn02 | fn03 fn01 | fn02 | fn03 fn01 | fn02 | fn03 (and you've verified that it's not null ), you still can't really call it. fn01 | fn02 | fn03 (并且您已经验证它不是null ),您仍然不能真正调用它。 Support for calling unions of functions was improved in TypeScript 3.5, but still, the only safe thing you can pass to a union of functions is the intersection of its parameters.在 TypeScript 3.5 中改进了对调用联合函数的支持,但您可以传递给函数联合的唯一安全的东西是其参数的交集 And the intersection string & string & number has no members (there are no values that are both string and number in JavaScript), meaning it is never , and thus it's completely uncallable.并且交集string & string & number没有成员(在 JavaScript 中没有既是string又是number的值),这意味着它never ,因此它是完全不可调用的。


So, that's why it doesn't work.所以,这就是为什么它不起作用。 To fix it, you'll either have to use function overloads , or annotate the function to be generic , where options is of generic type O extends Options .要修复它,您要么必须使用function 重载,要么将 function 注释为generic ,其中options是通用类型O extends Options

Overloads are easy but they are not really type safe.重载很容易,但它们并不是真正的类型安全。

The generic type has the potential to be more type safe, but not when you use switch , which exposes a current limitation of TypeScript whereby control flow analysis is unable to narrow the types of generic-typed variables.泛型类型有可能更安全,但当您使用switch时则不然,这暴露了TypeScript 的当前限制,控制流分析无法缩小泛型类型变量的类型。

The safest way to do it would be to use a mapping object instead of a switch statement:最安全的方法是使用映射 object 而不是switch语句:

const test = <O extends Options>(options: O) => ({
    op1: fn01,
    op2: fn02,
    op3: fn03
}[options]);

This is properly inferred to be a generic function where each input type maps to a particular function output type:这被正确推断为通用 function ,其中每个输入类型映射到特定的 function output 类型:

const chosenFN = test('op1'); // (name: string) => void
chosenFN("okay") // okay
test('op2')("age is a string I guess"); // okay
test('op3')(8675309); // okay

Link to code 链接到代码

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

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