You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular16+TypeScript5正式装饰器初始化setter传入undefined问题

问题根源分析

这是TypeScript 5标准装饰器与旧实验性装饰器的执行逻辑差异导致的:

  1. 旧实验性装饰器(experimentalDecorators: true):字段装饰器在类定义阶段直接修改原型,不会处理构造函数中对未初始化字段的默认赋值,因此未设置初始值的字段不会触发setter。
  2. TypeScript 5标准装饰器(遵循ECMAScript规范):字段装饰器通过context.addInitializer在实例初始化阶段定义存取器。而TS编译器会给所有未显式设置初始值的类字段,在构造函数中自动添加undefined赋值逻辑,这个赋值动作会触发我们定义的setter,导致sessionStorage中对应值被误删。
  3. 注意:!非空断言仅用于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 13:35:25