如何在 TypeScript 的泛型函数中使用判别式?
How to use discriminant in generic functions in TypeScript?
在使用带泛型的可区分联合时,我需要编写一些冗余代码,这也可能对类型安全造成危害。
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')
这样的调用将仍然编译。如何解决这个问题呢?有没有办法像以下版本之一一样定义函数?
fetch<T extends Shape>(type: T['kind'], id: string):T
fetch<T extends Shape['kind']>(type: T, id: string): SomeMagic<T>
和 SomeMagic<T>
帮助编译器找到合适的类型,比如 SomeMagic<'square'>
在编译时推断出 Square
?
我推荐您的 "version 2" 方法如下:
declare 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
"magic" 是 Extract
定义的 in the standard library, which is a conditional type 类型函数,它从联合中提取匹配元素。
您可以采用您的"version 1"方法,但我不推荐它:
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']
不是一个很好的推断点T
.
无论如何,希望对您有所帮助。祝你好运。
我们需要处理的第一件事是将形状与它们各自的种类绑定。您的原始解决方案:
/**
* 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
这样更好,但是 return 类型只是一个 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
这将为您提供准确的 return 类型,但代价是必须编写更多代码。有人可能还会争辩说存在一些冗余 — 形状与其种类之间的联系已经封装在其界面中,因此以重载的形式重复它似乎并不完美。
在使用带泛型的可区分联合时,我需要编写一些冗余代码,这也可能对类型安全造成危害。
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')
这样的调用将仍然编译。如何解决这个问题呢?有没有办法像以下版本之一一样定义函数?
fetch<T extends Shape>(type: T['kind'], id: string):T
fetch<T extends Shape['kind']>(type: T, id: string): SomeMagic<T>
和SomeMagic<T>
帮助编译器找到合适的类型,比如SomeMagic<'square'>
在编译时推断出Square
?
我推荐您的 "version 2" 方法如下:
declare 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
"magic" 是 Extract
定义的 in the standard library, which is a conditional type 类型函数,它从联合中提取匹配元素。
您可以采用您的"version 1"方法,但我不推荐它:
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']
不是一个很好的推断点T
.
无论如何,希望对您有所帮助。祝你好运。
我们需要处理的第一件事是将形状与它们各自的种类绑定。您的原始解决方案:
/**
* 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
这样更好,但是 return 类型只是一个 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
这将为您提供准确的 return 类型,但代价是必须编写更多代码。有人可能还会争辩说存在一些冗余 — 形状与其种类之间的联系已经封装在其界面中,因此以重载的形式重复它似乎并不完美。