如何在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.

