You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 20:24:59