如何在TypeScript的泛型函数中使用判别式?

时间:2018-12-23 03:07:45

标签: typescript generics discriminated-union

在使用带有泛型的区分联合时,我需要编写一些冗余代码,这也可能对类型安全性有害。

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}
type Shape = Square | Rectangle | Circle;

function<T extends Shape> fetch(type: Shape['kind'], id: string): T {
  // fetch data from database via id
}

问题是函数fetch中存在多余的类型说明,我可以使用Shape['kind']将类型限制为'square' | 'rectangle' | 'circle',但是像fetch<Square>('circle', 'some-id')这样的调用仍会编译。如何解决这个问题呢?有什么方法可以定义功能,例如以下版本之一?

  1. fetch<T extends Shape>(type: T['kind'], id: string):T
  2. fetch<T extends Shape['kind']>(type: T, id: string): SomeMagic<T>SomeMagic<T>可以帮助编译器找到正确的类型,例如SomeMagic<'square'>在编译时推断Square吗?

2 个答案:

答案 0 :(得分:2)

我建议您采用这种“版本2”方法:

Python -V

“魔术”是定义为in the standard librarydeclare function fetch<T extends Shape['kind']>( type: T, id: string ): Extract<Shape, {kind: T}>; fetch<'square'>('circle', 'some-id'); //error const shape = fetch('rectangle', 'some-id'); // shape is a Rectangle 类型的函数,它是conditional type,它从联合中提取匹配元素。

可以采用“版本1”方法,但我不建议这样做:

Extract

请注意,declare function fetch<T extends Shape>(type: T['kind'], id: string): T; fetch<Square>('circle', 'some-id'); //error const shape = fetch('rectangle', 'some-id'); // shape is a Shape 参数是type,而不是T['kind']。如前所述,它可以解决您的问题,但是如果您允许编译器从参数中推断出Shape['kind'],则最终只能推断出T,因为Shape对于T['kind']

无论如何,希望能有所帮助。祝你好运。

答案 1 :(得分:0)

我们需要处理的第一件事是将形状与它们各自的种类绑定在一起。您原来的解决方案:

/**
 * Bad — doesn't bind the `Shape` with its `kind`. It allows calling `fetch<Square>('circle', 'foo')`.
 */
declare function fetch<T extends Shape>(type: Shape['kind'], id: string): T;

相反,要告诉TypeScript type不仅需要任何种类,而且还必须完全是我们当前正在考虑的形状。

/**
 * Better: the `kind` is bound with its corresponding `Shape`. The downside: The exact return type is not inferred.
 */
declare function fetch<T extends Shape>(type: T['kind'], id: string): T;

const oups = fetch<Square>('circle', 'foo'); // $ExpectError
const shape = fetch('circle', 'foo');        // $ExpectType Shape

这更好,但是返回类型只是一个Shape。通过为您的函数指定重载,我们可以做得更好:

/**
 * Using overloads can help you determine the exact return type.
 */
declare function fetch(type: 'circle', id: string): Circle;
declare function fetch(type: 'square', id: string): Square;
declare function fetch(type: 'rectangle', id: string): Rectangle;

const circle = fetch('circle', 'foo'); // $ExpectType Circle

这将为您提供确切的返回类型,但要付出编写更多代码的代价。也许还会有人争辩说存在某种冗余-形状与其种类之间的联系已经封装在其接口中,因此以过载的形式重复它似乎并不完美。