TypeScript拼接已知字符串类型时如何推断模板字面量类型
问题背景
现有两个类型定义:Action 和 ActionEmitter,ActionEmitter由Action派生,规则为在Action的所有字符串字面量成员后追加固定后缀'Clicked'。
需要实现一个入参类型为Action、返回值类型为ActionEmitter的函数,期望TypeScript可以直接识别入参与'Clicked'拼接后的结果为合法的ActionEmitter类型,不需要额外编写类型守卫或类型转换逻辑。
原有代码如下:
type Action = 'next' | 'previous'; type ActionEmitter = Action extends `${infer u}` ? `${u}Clicked` : never; function isActionEmitter(s: string | ActionEmitter): s is ActionEmitter { switch (s) { case 'previousClicked': case 'nextClicked': return true; default: return false; } } // 可正常运行但存在冗余逻辑 function createActionEmitterGood(action: Action): ActionEmitter | null { const emitter = action + 'Clicked'; return (isActionEmitter(emitter) && emitter) || null; } // 类型报错:Type 'string' is not assignable to type '"nextClicked" | "previousClicked"' function createActionEmitterBad(action: Action): ActionEmitter | null { const clicked = 'Clicked' as const; return action + clicked; }
createActionEmitterGood通过类型守卫实现了需求但写法冗余,createActionEmitterBad直接拼接字符串会触发类型错误,目标是在不使用类型守卫、不做强制类型转换的前提下实现合法的函数逻辑。
实现方案
报错的核心原因:TypeScript 不会对+运算符拼接的字符串结果做模板字面量类型推导,所有通过+拼接得到的字符串,默认会被拓宽为string类型,无法匹配ActionEmitter这种精确的字面量联合类型。
不需要额外的校验或类型断言,只需要将+拼接替换为TypeScript的模板字符串语法即可,TS会对模板字符串形式的字面量拼接做精确的类型推导。同时由于入参已经被约束为合法的Action类型,拼接结果必然是合法的ActionEmitter成员,不需要返回null兜底。
最终可通过类型校验的代码如下:
type Action = 'next' | 'previous'; // ActionEmitter可以直接简化定义,和原有条件推导的效果完全等价 type ActionEmitter = `${Action}Clicked`; function createActionEmitter(action: Action): ActionEmitter { return `${action}Clicked`; }
以上代码无任何类型报错:传入'next'时TS自动推导返回值为'nextClicked',传入'previous'时自动推导返回值为'previousClicked',完全匹配ActionEmitter类型约束。
内容的提问来源于stack exchange,提问作者jack

