TypeScript继承类混合声明组合在Jest中运行报错求助
我之前也碰到过几乎一模一样的问题!这个报错大概率是Jest的转译/模块处理逻辑和TypeScript官方混入模式的实现不兼容导致的。
TypeScript官方的混入通常是通过动态生成子类、修改原型链的方式实现的,而Jest默认用Babel转译代码时,会把ES6类转译成ES5的构造函数形式。如果你的混入逻辑里存在直接调用类(比如Fairy()而不是new Fairy())的操作,转译后就会触发Cannot call a class as a function——因为ES5构造函数必须用new调用,但混入代码可能没处理好这一点。
至于为什么ts-node和Playground能正常运行?因为它们的执行环境更贴近原生TypeScript的处理逻辑,没有经过Babel那种深度转译,所以不会破坏混入的原型链和类调用逻辑。
下面给你几个亲测有效的修复方案,按优先级排序:
1. 检查混入函数的实现,避免直接调用类构造函数
先看你的混入函数是不是类似官方示例的写法:
function SpriteMixin<T extends new (...args: any[]) => any>(Base: T) { return class extends Base { // 混入的属性和方法 }; }
如果混入函数里有直接Base()的代码(没加new),那肯定会在Jest里报错。要确保所有类的实例化都通过new关键字,或者调整混入逻辑,让它只通过继承的方式扩展类,而不是直接调用构造函数。
2. 调整Jest的转译配置,适配TypeScript类
如果是Babel转译的锅,修改你的babel.config.js或者jest.config.js:
- 确保
@babel/preset-typescript和@babel/plugin-transform-classes的版本是最新的,后者能更好地处理类转译时的原型链问题。 - 在
jest.config.js里设置transformIgnorePatterns,别让Jest忽略你的类文件;如果项目用ES模块,开启useESM模式。
举个babel.config.js的配置例子:
module.exports = { presets: [ ['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript' ], plugins: [ '@babel/plugin-transform-classes' ] };
3. 改用装饰器实现混入(兼容性更好)
如果你的项目允许用装饰器,这种方式在Jest里的兼容性会好很多。比如:
// 定义混入装饰器 function ApplyMixin(mixinClass: new () => any) { return function(target: Function) { Object.assign(target.prototype, mixinClass.prototype); }; } // 使用装饰器 @ApplyMixin(YourMixinClass) class Sprite extends Fairy { constructor() { super(); // 一定要显式调用父类构造函数 // 你的初始化逻辑 } }
记得在tsconfig.json里开启装饰器支持:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
4. 显式处理中间类的构造函数
如果问题出在中间类Fairy身上,检查Sprite类有没有显式调用super()。Jest转译后,父类构造函数的调用逻辑可能会被混淆,显式调用super()能确保构造函数被正确初始化。
另外,要是混入函数需要传递参数给父类,一定要确保参数能正确传递到super()里。
修改完之后,先在ts-node里跑一遍确认正常,然后在Jest里加个调试日志:
console.log(typeof Fairy); console.log(Sprite.prototype instanceof Fairy);
如果打印出来typeof Fairy是function(正常,ES5转译后的类就是函数),但Sprite.prototype instanceof Fairy是false,那说明原型链被破坏了,得回头检查混入逻辑或者转译配置。
内容的提问来源于stack exchange,提问作者DjH

