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

如何在switch外部实现TypeScript类型收窄,避免代码冗余?

问题描述

我定义了如下接口与类型:

interface subA {
  attr1: number;
}

interface subB {
  attr2: number;
}

interface A {
  type: 'A';
  subList: subA[];
}

interface B {
  type: 'B';
  subList: subB[];
}


type AorB = A | B;
type subAorB<T extends AorB> = T extends A ? subA : subB;

并编写了如下函数:

function test(p: AorB) {
  const subList: subAorB<typeof p>[] = [];
  for (let i = 0; i < 10; i++) {
    switch (p.type) {
      case 'A': {
        const sub: subAorB<typeof p> = {
          attr1: i,
        };
        subList.push(sub);
        break;
      }

      case 'B': {
        const sub: subAorB<typeof p> = {
          attr2: i,
        };
        subList.push(sub);
        break;
      }
    }
  }
  p.subList = subList; // TS2322: Type (subA | subB)[] is not assignable to type subA[] | subB[]
}

在p.subList = subList;行出现TS2322错误:Type (subA | subB)[] is not assignable to type subA[] | subB[]。我期望subList被推断为subA[]或subB[],但由于该变量定义在switch外部,TypeScript将其推断为(subA | subB)[]。

我知道可以通过将subList定义在switch分支内来解决问题,但这种写法过于冗余:

function test(p: AorB) {
  switch (p.type) {
    case 'A': {
      const subList: subAorB<typeof p>[] = [];
      for (let i = 0; i < 10; i++) {
        const sub: subAorB<typeof p> = {
          attr1: i,
        };
        subList.push(sub);
      }
      p.subList = subList;
      break;
    }

    case 'B': {
      const subList: subAorB<typeof p>[] = [];
      for (let i = 0; i < 10; i++) {
        const sub: subAorB<typeof p> = {
          attr2: i,
        };
        subList.push(sub);
      }
      p.subList = subList;
      break;
    }
  }
}

请问如何在switch外部定义subList时,让TypeScript正确将其推断为subA[]或subB[]?


解决方案

1. 将函数改为泛型函数

通过泛型约束把参数p的类型和subList的类型强绑定,让TypeScript明确两者的关联关系:

function test<T extends AorB>(p: T) {
  const subList: subAorB<T>[] = [];
  for (let i = 0; i < 10; i++) {
    switch (p.type) {
      case 'A': {
        const sub = { attr1: i } as subAorB<T>;
        subList.push(sub);
        break;
      }
      case 'B': {
        const sub = { attr2: i } as subAorB<T>;
        subList.push(sub);
        break;
      }
    }
  }
  p.subList = subList; // 类型完全匹配,无错误
}

泛型T会捕获p的具体类型(A或B),subAorB<T>会被精确推断为subA或subB,subList的类型也就明确为subA[]或subB[],和p.subList的类型完全对齐。

2. 结合类型守卫与类型断言

如果不想用泛型,也可以在循环分支里通过类型断言指定subList的具体类型,配合switch的类型守卫实现类型匹配:

function test(p: AorB) {
  let subList: subA[] | subB[] = [];
  for (let i = 0; i < 10; i++) {
    switch (p.type) {
      case 'A': {
        const sub: subA = { attr1: i };
        (subList as subA[]).push(sub);
        break;
      }
      case 'B': {
        const sub: subB = { attr2: i };
        (subList as subB[]).push(sub);
        break;
      }
    }
  }
  p.subList = subList; // 类型匹配
}

这里将subList初始化为subA[] | subB[],在每个分支里用断言明确当前操作的数组类型,最后赋值时TypeScript能识别到subList的类型与p.subList一致。

3. 利用const断言生成精确类型数组

通过创建一个返回精确类型元素的函数,结合数组生成方法一次性构建subList:

function test(p: AorB) {
  const createSub = (i: number) => {
    switch (p.type) {
      case 'A': return { attr1: i } as const;
      case 'B': return { attr2: i } as const;
    }
  };
  const subList = Array.from({ length: 10 }, (_, i) => createSub(i)) as subAorB<typeof p>[];
  p.subList = subList;
}

createSub函数通过const断言返回精确的字面量类型,Array.from生成的数组会被推断为对应类型的数组,最后通过断言确认类型即可完成赋值。


内容的提问来源于stack exchange,提问作者N.F.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:23:21