如何根据输入参数键入返回类型

时间:2020-04-22 15:12:04

标签: typescript typescript-generics

我有一个函数,该函数接收带有options属性的kind自变量。 kind的可能值是一小组值。所以基本上是一个枚举。

取决于kind的函数应该具有不同的返回值。虽然所有可能的返回值都从某种常见的基本类型扩展而来。

我可以通过重载实现我想要的功能,但是函数本身的类型不是很好:

function test(options: Scenarios['bar']['options']): Scenarios['bar']['ret'];
function test(options: Scenarios['foo']['options']): Scenarios['foo']['ret'];
function test(options: any): any {
  ...
};

是否有使用泛型键入此内容的好方法? if(options.kind === 'foo') { return ... }还将正确地强制使用正确的返回类型将是完美的。

那是我尝试过的,但是没有用。

type Base {
  a: string;
}

type Foo {
  b: string;
}

type Bar {
  c: string;
}

interface Scenarios {
  foo: { options: { kind: 'foo', input: string }, ret: Foo },
  bar: { options: { kind: 'bar' }, ret: Bar },
}

function test<S extends keyof Scenarios, O extends Scenarios[S]['options'], R extends Scenarios[S]['ret']>(options: O): R {
  const out: Partial<R> = {
    a: 'one',
  };
  if(options.kind === 'foo') {
    out.b = options.input;
  }
  if(options.kind === 'bar') {
    out.c = "whatever"
  }

  return out;
}

这里OR似乎都没有正确输入。我收到多个错误:

    对象文字的
  • a: 'one',错误可能仅指定了已知的属性,而'Part'类型的'a'不存在
  • 类型为'O'的属性
  • options.input错误不存在。
  • 属性“ b”的
  • out.b(和out.c)错误在“部分”类型上不存在。

1 个答案:

答案 0 :(得分:1)

我并不是这个领域的专家,但是经过您的请求一段时间后,我意识到了一些事情:

  1. TypeScript只能区分联合类型;它不能区分泛型。
  2. 上述歧视并不向上传播;也就是说,TypeScript不会基于对子对象的区分而自动区分父对象。

这就是我想出的一个解决方案:

type Base = { // I'm assuming this is the base type for Foo and Bar
    a: string;
}

interface Foo extends Base { // so I shall modify here a bit
    b: string;
}

interface Bar extends Base { // and here of course
    c: string;
}

interface Scenarios {
    // Now I put the return type inside the option, to make the discrimination work
    // Of course, I need to make 'ret' optional, or your input won't make sense
    foo: { kind: 'foo', input: string, ret?: Foo },
    bar: { kind: 'bar', ret?: Bar },
}

// Notice the use of 'Required' here; that brings the actual type of 'ret' back
function test<X extends keyof Scenarios>(options: Scenarios[X]): Required<Scenarios[X]>['ret'] {
    let data = options as Scenarios[keyof Scenarios]; // create a union type
    data.ret = { a: 'one' } as Scenarios[keyof Scenarios]['ret']; // type assertion

    if (data.kind === 'foo') {
        data.ret!.b = data.input; // finally the discrimination works!
    }
    if (data.kind === 'bar') {
        data.ret!.c = "whatever"
    }
    return data.ret!;
}

好吧,到目前为止,很好,不幸的是,我仍然无法使TypeScript自动推断通用参数。说如果我跑步:

var result = test({ kind: 'foo', input: "aas" }); // oops

然后TypeScript仍然无法确定result的类型为Foo。但是,当然,这种自动推断的实用价值非常低,因为即使有效,它也只有在您在函数的参数中按字面意思键入“ foo”时才有效,并且如果可以,是什么让您无法键入通用参数?

您可以在此Playground Link

中尝试这些代码

更新:

我刚刚找到了我上一个问题的解决方案:

function test<X extends keyof Scenarios>(options: { kind: X } & Scenarios[X]): Required<Scenarios[X]>['ret'] {
    ....
}

var result = test({ kind: 'foo', input: "aas" }); // It works!

技巧是将{ kind: X }添加到参数声明中。

请参阅此Playground Link