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

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

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

是否可以缩小doubleFn的类型,以使其理解为与给定形状的匹配对?

Link to TypeScript playground with the same code

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);
yjy611 回答:根据枚举成员区分函数和对象类型

此问题与我一直所说的“相关记录”(请参阅​​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之间的 correlation


在TypeScript中,目前没有很好的方法来处理此问题。正如您指出的那样,您可以使用开关/大小写或其他条件代码来提示编译器使用control flow analysis,并确保在每种情况下调用都是安全的。但这是多余的。

当前唯一的直接解决方案是使用type assertion来告诉编译器您已确保自己在做的事是安全的。这是有风险的,因为您有责任自行检查类型安全性。因此,如果要使用类型断言,通常最好将它们限制为少量代码,以便在其他地方重复使用:

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),但实际上并没有获得任何吸引力。因此,我建议您现在就充分重构代码,以使必要的类型断言的数量足够小以至于易于管理。

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

Playground link to code

本文链接:https://www.f2er.com/2353370.html

大家都在问