TypeScript如何正确推断包含多个Error子类的函数返回值类型
问题描述
我有一个函数可以返回number、Error或者NegativeError类型的值,代码示例如下:
class NegativeError extends Error { name = 'NegativeError' } function doThing (a: number): number | NegativeError | Error { return a < 0 ? new NegativeError() : a === 0 ? new Error() : a } const done = doThing(1) // number | NegativeError | Error
但是当我移除函数签名中的显式返回类型后,TypeScript会忽略Error类型,仅推断返回类型为number | NegativeError:
function doThing (a: number) { return a < 0 ? new NegativeError() : a === 0 ? new Error() : a } const done = doThing(1) // number | NegativeError
我目前可以通过一个hack方案强制保留所有离散返回类型:使用构造函数实现,且可以维持原型继承关系,代码如下:
function NegativeError(message: string) { Error.call(this) this.message = message this.name = 'NegativeError' } NegativeError.prototype = Object.create(Error.prototype) NegativeError.prototype.constructor = NegativeError
但我不想使用该方案,因为它可读性差,还会触发TypeScript报错:An outer value of 'this' is shadowed by this container。请问有没有方法可以让TypeScript正确推断所有返回类型?
解决方案
这个问题是TypeScript结构化类型系统的正常表现:你定义的NegativeError继承自Error,且没有显式标注name属性的字面量类型,TS会将name推断为通用string类型,此时NegativeError和原生Error结构完全兼容,TS在推断联合类型时会自动折叠父类型,只保留子类型NegativeError,所以原生Error类型会被“吞掉”。
你可以通过以下几种简洁的方案解决,完全不需要用老式构造函数的hack写法:
方案1:给自定义Error类的
name属性加显式字面量类型声明,让TS识别到它和原生Error的结构差异class NegativeError extends Error { // 显式标注name为固定字面量类型,不再是通用string name: 'NegativeError' = 'NegativeError' } function doThing (a: number) { return a < 0 ? new NegativeError() : a === 0 ? new Error() : a } // 此时会正确推断返回类型为 number | NegativeError | Error const done = doThing(1)方案2:如果不想修改自定义Error类的定义,可以在返回原生
Error的位置加个弱类型断言,强制TS保留该类型function doThing (a: number) { return a < 0 ? new NegativeError() : a === 0 ? new Error() as Error : a }方案3:如果函数返回类型固定,更推荐直接显式声明函数返回类型,既能避免类型推断的偏差,也能提升代码的可维护性,后续修改函数逻辑时如果意外改变返回类型TS会直接提示报错。
内容的提问来源于stack exchange,提问作者Matt K

