根据枚举成员区分函数和对象类型



在使用索引签名和联合类型时,我试图在 TypeScript 中缩小类型,而不直接区分它们,例如使用 switch case 语句。

下面的代码在使用形状调用 doubleFn 变量时会引发错误,尽管在运行时形状正确返回为圆,但 doubleFn 被推断为将半径加倍的函数,并且调用它也可以工作。

是否可以缩小doubleFn的类型,以便它理解它是给定形状的匹配对?

链接到具有相同代码的 TypeScript 游乐场

enum Shapes {
Circle,
Square,
}
interface ShapeProperties {
[Shapes.Circle]: {
radius: number;
};
[Shapes.Square]: {
length: number;
};
}
type FunctionsType = {
[key in Shapes]: (a: ShapeProperties[key]) => ShapeProperties[key];
};
const doubleFunctions: FunctionsType = {
[Shapes.Circle]: (circleProps: ShapeProperties[Shapes.Circle]) => ({
radius: circleProps.radius * 2,
}),
[Shapes.Square]: (squareProps: ShapeProperties[Shapes.Square]) => ({
length: squareProps.length * 2,
}),
};
interface Circle {
type: Shapes.Circle;
props: ShapeProperties[Shapes.Circle];
}
interface Square {
type: Shapes.Square;
props: ShapeProperties[Shapes.Square];
}
type Shape = Circle | Square;
function getShape(): Shape {
return { type: Shapes.Circle, props: { radius: 5 } };
}
const shape = getShape();
const doubleFn = doubleFunctions[shape.type];
doubleFn(shape.props);

这个问题与我一直所说的"相关记录"密切相关(参见 microsoft/TypeScript#30581(; 编译器并不真正知道如何跟踪不同联合类型值之间的相关性,因此它假设它们是独立的,并且在这样的情况下会出现错误。

在此特定情况下,您有doubleFunshape.props,编译器将它们中的每一个都视为联合类型:

const doubleFn = doubleFunctions[shape.type];
/* const doubleFn: 
((a: {radius: number;}) => {radius: number;}) | 
((a: {length: number;}) => {length: number;}) 
*/
const props = shape.props;
/* const props: {radius: number;} | {length: number;} */

这些类型并不正确,但它们不足以让编译器意识到调用doubleFn(props)是安全的:

doubleFn(props); // error!

编译器抱怨说,也许doubleFn会变成平方处理函数,而props会变成圆属性......反之亦然。 这在运行时确实是一个错误。doubleFnprops之间的相关性未在类型系统中表示。


目前在TypeScript中没有很好的方法来解决这个问题。 如前所述,您可以使用开关/大小写或其他条件代码来提示编译器使用控制流分析,并查看在每种情况下调用是否安全。 但这是多余的。

目前唯一的其他直接解决方案是使用类型断言来告诉编译器您已经确保您正在做的事情是安全的。 这是有风险的,因为您自己承担了验证类型安全性的责任。 因此,如果要使用类型断言,通常最好将它们限制在其他地方重用的一小部分代码中:

const toShapeFunction = (f: FunctionsType) => <S extends Shape>(s: S) =>
(f[s.type] as (s: S['props']) => S['props'])(s.props);

该函数toShapeFunction()将类型为FunctionsType的参数转换为在任何Shape上运行并生成相应属性类型输出的函数。 类型断言是我们告诉编译器:"f[s.type]将接受s.props类型的值并生成相同类型的值"。 编译时没有错误。 然后,您可以放心使用toShapeFunction()

const doubleFunction = toShapeFunction(doubleFunctions);
const newSh = doubleFunction(shape); // okay
const ci: Circle = {
type: Shapes.Circle,
props: { radius: 10 }
}
const newCi = doubleFunction(ci);
console.log(newCi.radius); // 20
const sq: Square = {
type: Shapes.Square,
props: { length: 35 }
}
const newSq = doubleFunction(sq);
console.log(newSq.length); // 70

看起来不错。


所以情况就是这样。 我曾经建议一些方法来减少处理相关值的痛苦(参见microsoft/TypeScript#25051(,但它并没有真正得到任何牵引力。 因此,我现在建议您对代码进行足够的重构,以便所需的类型断言数量足够小,以便于管理。

好的,希望有帮助;祝你好运!

操场链接到代码

最新更新