TypeScript条件返回类型:单条件正常多条件报错求因
可辨识联合工厂函数新增类型后类型不兼容问题
我实现了一个基于可辨识联合参数的形状工厂函数,代码如下:
interface CircleArgs { type: "circle", radius: number }; interface SquareArgs { type: "square", length: number }; type ShapeArgs = CircleArgs | SquareArgs; class Circle { constructor(_: CircleArgs) {}} class Square { constructor(_: SquareArgs) {}} type Shape = Circle | Square; type ShapeOfArgs<Args extends ShapeArgs> = Args extends CircleArgs? Circle : Square; function createShape<Args extends ShapeArgs>(args: Args): ShapeOfArgs<Args> { switch (args.type) { case "circle": return new Circle(args); case "square": return new Square(args); } }
这个实现能让TypeScript根据参数正确推断返回类型:
const circle1 = createShape({type: "circle", radius: 1}); // 推断为Circle const square1 = createShape({type: "square", length: 1}); // 推断为Square
同时在包装函数中也能正确传递返回类型:
class Container { insert(_: Shape): void {}; } function createShapeIn<Args extends ShapeArgs>(args: Args, cont: Container) { const shape = createShape(args); cont.insert(shape); return shape; } const cont = new Container(); const circle2 = createShapeIn({type: "circle", radius: 1}, cont); // 仍推断为Circle const square2 = createShapeIn({type: "square", length: 1}, cont); // 仍推断为Square
但新增一种形状类型后,代码无法编译:
新增Figure类型:
interface FigureArgs { type: "figure", amount: number }; class Figure { constructor(_: FigureArgs) {}}
更新联合类型:
type ShapeArgs = CircleArgs | SquareArgs | FigureArgs; type Shape = Circle | Square | Figure;
修改条件返回类型:
type ShapeOfArgs<Args extends ShapeArgs> = Args extends CircleArgs? Circle : Args extends SquareArgs? Square : Figure;
此时工厂函数出现类型不兼容报错:
function createShape<Args extends ShapeArgs>(args: Args): ShapeOfArgs<Args> { switch (args.type) { case "circle": return new Circle(args); // 错误:Circle无法赋值给ShapeOfArgs<Args> case "square": return new Square(args); // 错误:Square无法赋值给ShapeOfArgs<Args> case "figure": return new Figure(args); // 错误:Figure无法赋值给ShapeOfArgs<Args> } }
请问我哪里出错了?
注:我知道可以用as断言强制通过,但这违背了类型检查的初衷。
问题原因
当你使用泛型Args extends ShapeArgs时,TypeScript无法在switch的每个分支中确定ShapeOfArgs<Args>的具体类型。因为Args是一个泛型参数,它可以是ShapeArgs联合中的任意成员,甚至是整个联合类型本身。TypeScript无法在分支中缩小泛型参数的范围,导致它无法验证new Circle(args)确实符合ShapeOfArgs<Args>的类型约束。
解决方案
方案1:利用可辨识联合自动推断(最简单)
不需要泛型和条件类型,直接让函数参数为ShapeArgs联合类型,TypeScript会自动根据type字段推断返回类型:
function createShape(args: ShapeArgs): Shape { switch (args.type) { case "circle": return new Circle(args); case "square": return new Square(args); case "figure": return new Figure(args); } }
方案2:函数重载保留精确类型推断
如果需要让调用者得到具体的Circle/Square/Figure类型而非Shape联合,可以用函数重载定义每个分支的输入输出关系:
function createShape(args: CircleArgs): Circle; function createShape(args: SquareArgs): Square; function createShape(args: FigureArgs): Figure; function createShape(args: ShapeArgs): Shape { switch (args.type) { case "circle": return new Circle(args); case "square": return new Square(args); case "figure": return new Figure(args); } }
方案3:映射类型维护类型关联
用映射类型统一管理参数和形状的对应关系,避免手动编写条件类型,同时保留泛型的灵活性:
type ShapeMap = { circle: Circle; square: Square; figure: Figure; }; type ShapeArgs = { [K in keyof ShapeMap]: { type: K } & ( K extends "circle" ? { radius: number } : K extends "square" ? { length: number } : { amount: number } ); }[keyof ShapeMap]; function createShape<K extends keyof ShapeMap>(args: Extract<ShapeArgs, { type: K }>): ShapeMap[K] { switch (args.type) { case "circle": return new Circle(args) as ShapeMap[K]; case "square": return new Square(args) as ShapeMap[K]; case "figure": return new Figure(args) as ShapeMap[K]; } }
这里的as断言是安全的,因为我们已经通过Extract和switch分支确保了类型匹配,不会违背类型检查的初衷。
内容的提问来源于stack exchange,提问作者Levi Haskell
相关产品推荐
相关产品推荐

