Angular16+TypeScript5正式装饰器初始化setter传入undefined问题
问题根源分析
这是TypeScript 5标准装饰器与旧实验性装饰器的执行逻辑差异导致的:
- 旧实验性装饰器(
experimentalDecorators: true):字段装饰器在类定义阶段直接修改原型,不会处理构造函数中对未初始化字段的默认赋值,因此未设置初始值的字段不会触发setter。 - TypeScript 5标准装饰器(遵循ECMAScript规范):字段装饰器通过
context.addInitializer在实例初始化阶段定义存取器。而TS编译器会给所有未显式设置初始值的类字段,在构造函数中自动添加undefined赋值逻辑,这个赋值动作会触发我们定义的setter,导致sessionStorage中对应值被误删。 - 注意:
!非空断言仅用于TypeScript类型检查,运行时该字段仍然会被赋值undefined。
最优解决方案
我们可以通过标记初始化状态来区分「构造阶段的默认undefined赋值」和「用户主动设置的undefined」,具体修改装饰器代码如下:
export function MyStorage(name: string) { console.log('factory method called'); return function (...args: unknown[]) { if (args != null && args.length === 2 && isClassFieldDecoratorContext(args[1])) { const context: ClassFieldDecoratorContext = args[1] as ClassFieldDecoratorContext; // 用Symbol作为私有标记,避免与用户字段冲突 const initCompletedFlag = Symbol(`__${String(context.name)}_init_done__`); context.addInitializer(function (this: any) { // 初始化标记设为未完成 this[initCompletedFlag] = false; Object.defineProperty(this, context.name, { set(newValue) { // 忽略构造阶段的默认undefined赋值 if (!this[initCompletedFlag] && newValue === undefined) { this[initCompletedFlag] = true; return; } // 标记初始化完成,后续所有赋值正常处理 this[initCompletedFlag] = true; console.log(`set ${String(context.name)} to ${newValue} with type ${typeof newValue}`); if (newValue == null) { sessionStorage.removeItem(name); } else { sessionStorage.setItem(name, newValue); } }, get() { const result = sessionStorage.getItem(name); console.log(`return ${String(context.name)} as ${result} with type ${typeof result}`); return result; }, // 保持字段的可枚举性,避免Angular变更检测等逻辑异常 enumerable: true, configurable: true }); // 初始化时同步sessionStorage已有值到实例(可选,确保组件初始化时字段值与存储一致) const storedValue = sessionStorage.getItem(name); if (storedValue !== null) { this[initCompletedFlag] = true; this[context.name] = storedValue; } else { this[initCompletedFlag] = true; } }); } }; } function isClassFieldDecoratorContext(arg: unknown): arg is ClassFieldDecoratorContext { return arg != null && typeof arg === 'object' && (arg as ClassFieldDecoratorContext).kind === 'field'; }
为什么临时方案不可取
直接在setter中忽略undefined会导致用户主动设置undefined时无法触发sessionStorage删除逻辑,违背了装饰器设计初衷(同步null/undefined到存储)。而通过初始化标记的方式,既能避免构造阶段的误操作,又能保留对用户主动设置undefined的正常处理。
内容的提问来源于stack exchange,提问作者letmejustfixthat
相关产品推荐
相关产品推荐

