TypeScript:如何根据函数参数对象的辨别属性收窄返回类型
解决辨别联合类型对象迁移时的函数内部类型收窄问题
你的场景是将旧版辨别联合类型(ShapeV1)转换为新版(ShapeV2),外部调用时类型推断正常,但函数内部返回值出现类型错误。这是因为TypeScript在泛型函数的分支逻辑中,无法自动将泛型参数TShape与具体的ShapeV2子类型建立精确关联。
问题代码回顾
type CircleV1 = { type: "circle"; colour: string; }; type CircleV2 = { type: "circle"; colour: string; sides: 1; }; type SquareV1 = { type: "square"; colour: string; }; type SquareV2 = { type: "square"; colour: string; sides: 4; }; type ShapeV1 = CircleV1 | SquareV1; type ShapeV2 = CircleV2 | SquareV2; type GetTypeName<T extends ShapeV1> = T extends { type: infer TypeName } ? TypeName : never; function convertToV2<TShape extends ShapeV1>( v1Shape: TShape ): Extract<ShapeV2, { type: GetTypeName<TShape> }> { switch (v1Shape.type) { case "circle": // 类型错误:TShape & { sides: 1; } 无法赋值给目标类型 return { ...v1Shape, sides: 1, }; case "square": return { ...v1Shape, sides: 4, }; } } // 外部调用正常推断为CircleV2 const shape = convertToV2({ type: "circle", colour: "red" });
解决方案1:使用函数重载签名
通过重载签名明确每种输入类型对应的返回类型,让TypeScript在分支中能精确匹配类型:
type CircleV1 = { type: "circle"; colour: string }; type CircleV2 = { type: "circle"; colour: string; sides: 1 }; type SquareV1 = { type: "square"; colour: string }; type SquareV2 = { type: "square"; colour: string; sides: 4 }; type ShapeV1 = CircleV1 | SquareV1; type ShapeV2 = CircleV2 | SquareV2; // 重载签名:明确每种输入对应的输出 function convertToV2(v1Shape: CircleV1): CircleV2; function convertToV2(v1Shape: SquareV1): SquareV2; // 实现签名:处理联合类型 function convertToV2(v1Shape: ShapeV1): ShapeV2 { switch (v1Shape.type) { case "circle": return { ...v1Shape, sides: 1 }; case "square": return { ...v1Shape, sides: 4 }; default: throw new Error(`Unknown shape type: ${v1Shape.type}`); } } // 正确推断为CircleV2 const circle = convertToV2({ type: "circle", colour: "red" }); // 正确推断为SquareV2 const square = convertToV2({ type: "square", colour: "blue" });
解决方案2:建立V1到V2的映射类型+泛型约束
如果需要保留泛型的灵活性(比如未来扩展更多Shape类型),可以先定义V1到V2的映射关系,再用泛型约束关联类型:
type CircleV1 = { type: "circle"; colour: string }; type CircleV2 = { type: "circle"; colour: string; sides: 1 }; type SquareV1 = { type: "square"; colour: string }; type SquareV2 = { type: "square"; colour: string; sides: 4 }; type ShapeV1 = CircleV1 | SquareV1; type ShapeV2 = CircleV2 | SquareV2; // 建立V1到V2的映射类型 type ShapeV1ToV2 = { circle: CircleV2; square: SquareV2; }; function convertToV2<T extends ShapeV1>( v1Shape: T ): ShapeV1ToV2[T["type"]] { switch (v1Shape.type) { case "circle": // 用类型断言明确当前分支的返回类型 return { ...v1Shape, sides: 1 } as ShapeV1ToV2["circle"]; case "square": return { ...v1Shape, sides: 4 } as ShapeV1ToV2["square"]; default: throw new Error(`Unknown shape type: ${v1Shape.type}`); } } // 正确推断为CircleV2 const circle = convertToV2({ type: "circle", colour: "red" }); // 正确推断为SquareV2 const square = convertToV2({ type: "square", colour: "blue" });
原因解释
原代码中,泛型参数TShape是ShapeV1的子类型,但TypeScript无法在switch分支中自动将TShape与Extract<ShapeV2, { type: GetTypeName<TShape> }>的具体子类型绑定。重载签名或映射类型能直接建立输入输出的精确关联,消除内部类型不匹配的错误。
内容的提问来源于stack exchange,提问作者Gregor
相关产品推荐
相关产品推荐

