TypeScript泛型字符串字面量类型参数的返回类型报错问题
TypeScript泛型函数switch分支返回类型不匹配问题解析
我编写了如下TypeScript代码:
function assertNever(x: never): never { throw new Error('Should never occur.') } type StudentMessage = | 'get-grades' | 'get-best-grade' | 'add-grade' type MessageFunction<TMessage extends StudentMessage> = TMessage extends 'get-grades' ? () => number[] : TMessage extends 'get-best-grade' ? () => number : TMessage extends 'add-grade' ? (grade: number) => void : never function Student(grades: number[]){ const _grades = [...grades]; return function<TMessage extends StudentMessage>(message: TMessage): MessageFunction<TMessage> { switch(message){ case 'get-grades': return () => _grades; case 'get-best-grade': return () => Math.min(..._grades); case 'add-grade': return (grade: number) => {_grades.push(grade)}; default: assertNever(message) } } }
运行时出现以下错误:
Type '() => number[]' is not assignable to type 'MessageFunction<TMessage>'. Type '() => number' is not assignable to type 'MessageFunction<TMessage>'. Type '(grade: number) => void' is not assignable to type 'MessageFunction<TMessage>'.
明明TypeScript能推断每个case的正确类型,却还是抛出这些错误,这是为什么?
问题原因
TypeScript的泛型参数TMessage代表的是StudentMessage的任意子集(可能是单个类型,也可能是联合类型),而在switch的每个分支里,编译器没办法把当前分支的具体消息类型和泛型TMessage做关联。
举个例子:当TMessage是'get-grades' | 'add-grade'这种联合类型时,MessageFunction<TMessage>会变成(() => number[]) | ((grade: number) => void),但当前分支返回的只是其中一种类型,编译器没办法确认这个返回值能覆盖所有可能的TMessage情况,所以会抛出类型不匹配的错误。
简单说就是:泛型参数是“提前声明的抽象类型”,而switch分支是“具体的类型分支”,两者之间的类型关联没办法被TypeScript的编译器自动推导出来。
解决方案
方案1:类型断言
在每个return语句后添加类型断言,明确告诉编译器当前返回值就是MessageFunction<TMessage>对应的类型,由于我们有assertNever兜底,不会出现类型不安全的情况:
function Student(grades: number[]){ const _grades = [...grades]; return function<TMessage extends StudentMessage>(message: TMessage): MessageFunction<TMessage> { switch(message){ case 'get-grades': return (() => _grades) as MessageFunction<TMessage>; case 'get-best-grade': return (() => Math.min(..._grades)) as MessageFunction<TMessage>; case 'add-grade': return ((grade: number) => {_grades.push(grade)}) as MessageFunction<TMessage>; default: assertNever(message) } } }
方案2:使用对象映射替代switch
这种写法更符合TypeScript的类型推断逻辑,编译器能自动关联消息类型和对应的函数类型,不需要额外断言:
function assertNever(x: never): never { throw new Error('Should never occur.') } type StudentMessage = | 'get-grades' | 'get-best-grade' | 'add-grade' type MessageFunctions = { 'get-grades': () => number[], 'get-best-grade': () => number, 'add-grade': (grade: number) => void } function Student(grades: number[]){ const _grades = [...grades]; const handlers: MessageFunctions = { 'get-grades': () => _grades, 'get-best-grade': () => Math.min(..._grades), 'add-grade': (grade: number) => _grades.push(grade) } return function<TMessage extends StudentMessage>(message: TMessage): MessageFunctions[TMessage] { return handlers[message] ?? assertNever(message); } }
内容的提问来源于stack exchange,提问作者Ember
相关产品推荐
相关产品推荐

