TypeScript 5属性装饰器转换报错:context.addInitializer不是函数
问题描述
我有如下旧版PropertyDecorator:
const bound = (method: any): PropertyDecorator => ((target: any, name?: PropertyKey): any => { if(name === undefined) { throw new Error('Bound decorator must be used with a property name.'); } return { get(): any { return target[method].bind(this); } }; }); class Foo { @bound('bar') foo!: any; bar(): string { return 'bar'; } }
想要将其转换为TypeScript 5装饰器,我的尝试代码如下:
const bound = (< This, Value >(method: keyof This): any => (( _value: undefined, context: ClassFieldDecoratorContext<This, Value> ) => { if(context.private) { throw new Error('Bound decorator cannot be used on private properties.'); } context.addInitializer(function(this: This) { (this as any)[method] = (this[method] as CallableFunction).bind(this); }); }));
该装饰器编译后的JS输出为:
const o = (i) => (n, r) => { if (r.private) throw new Error("Bound decorator cannot be used on private properties."); r.addInitializer(function() { this[i] = this[i].bind(this); }); }; export { o as bound };
运行时出现错误:TypeError: context.addInitializer is not a function,我的tsconfig配置如下:
{ "compilerOptions": { "allowUnreachableCode": false, "allowUnusedLabels": false, "alwaysStrict": true, "exactOptionalPropertyTypes": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": true, "noImplicitOverride": true, "noImplicitReturns": true, "noImplicitThis": true, "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, "strict": true, "strictBindCallApply": true, "strictFunctionTypes": true, "strictNullChecks": true, "strictPropertyInitialization": true, "useUnknownInCatchVariables": true, "module": "es6", "moduleResolution": "node10", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": [ "es2017", "dom" ], "target": "es2017", "skipLibCheck": true, "composite": true, "rootDir": "./src", "outDir": "./dist" }, "files": [ "./src/bound.ts", "./src/index.ts" ], "include": [ "./src/**/*.ts" ], "exclude": [ "./node_modules", "./dist" ] }
问题原因与解决方案
错误根源
你启用了旧版装饰器开关experimentalDecorators,但编写的是TypeScript 5的新装饰器语法,两者完全不兼容。旧版装饰器的上下文对象没有addInitializer方法,因此触发报错。
修正步骤
1. 调整tsconfig配置
关闭旧版装饰器,启用新版装饰器模式:
{ "compilerOptions": { // 保留原有严格模式等配置 "experimentalDecorators": false, // 关闭旧版装饰器 "emitDecoratorMetadata": false, // 新版装饰器暂不支持该特性,必须关闭 "module": "ESNext", // 新版装饰器依赖ES模块 "target": "ES2022", // 至少需要ES2022支持类字段相关特性 "decorators": "experimental", // TS 5.2+ 启用新版实验性装饰器 // 其他原有配置不变 } }
注:TS 5.0-5.1版本需用
experimentalDecorators: true+emitDecoratorMetadata: false来启用新版装饰器,但需确保项目未混用旧版语法。
2. 修正装饰器逻辑
你的新版代码逻辑与旧版不符:旧版是给装饰的字段绑定另一个方法的引用,而你写的代码在修改目标方法本身。正确的实现应该是给装饰的字段返回绑定后的方法:
const bound = <This, Method extends (...args: any[]) => any>( methodName: keyof This & string ) => { return function ( _value: undefined, context: ClassFieldDecoratorContext<This, Method> ) { if (context.private) { throw new Error('Bound decorator cannot be used on private properties.'); } // 返回字段初始化函数,赋值为绑定后的目标方法 return function (this: This) { return (this[methodName] as Method).bind(this); }; }; }; // 使用示例 class Foo { @bound('bar') foo!: () => string; bar(): string { return 'bar'; } } // 测试 const fooInstance = new Foo(); console.log(fooInstance.foo()); // 输出 'bar'
3. 编译验证
修正后编译生成的JS会符合新版装饰器规范,上下文对象会正确提供addInitializer(如需使用初始化逻辑),或直接返回字段初始化函数,不会再触发原错误。
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

