【问题标题】:Method decorator that allows to execute a decorated method only once Typescript允许只执行一次装饰方法的方法装饰器打字稿
【发布时间】:2023-01-04 05:00:19
【问题描述】:

我的任务是实现一个只允许执行一次装饰方法的方法装饰器。
例如:

   class Test {
        data: any;
        @once
        setData(newData: any) {
            this.newData = newData;
        }
    }
    const test = new Test();
    test.setData([1,2,3]);
    console.log(test.data); // [1,2,3]
    test.setData('new string');
    console.log(test.data); // [1,2,3]  

我尝试了很多组合来使一个被调用两次的函数什么都不做,但这不是我应该拥有的,单元测试失败了,所以,这就是我到目前为止所拥有的:

const once = (
    target: Object,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
) => {
    const method = descriptor.value;
    
    descriptor.value = function (...args){
        // ???
    }
};  

单元测试:

describe('once', () => {
    it('should call method once with single argument', () => {
        class Test {
            data: string;
            @once
            setData(newData: string) {
                this.data = newData;
            }
        }
        const test = new Test();
        test.setData('first string');
        test.setData('second string');
        assert.strictEqual(test.data, 'first string')
    });

    it('should call method once with multiple arguments', () => {
        class Test {
            user: {name: string, age: number};
            @once
            setUser(name: string, age: number) {
                this.user = {name, age};
            }
        }
        const test = new Test();
        test.setUser('John',22);
        test.setUser('Bill',34);
        assert.deepStrictEqual(test.user, {name: 'John', age: 22})
    });

    it('should return always return first execution result', () => {
        class Test {
            @once
            sayHello(name: string) {
                return `Hello ${name}!`;
            }
        }
        const test = new Test();
        test.sayHello('John');
        test.sayHello('Mark');
        assert.strictEqual(test.sayHello('new name'), 'Hello John!')
    })
});  

请问你能帮帮我吗?提前致谢!

【问题讨论】:

  • 很好的问题——我在一两周前遇到了同样的问题,并且在放弃之前玩了很久。我并不经常需要那个功能,所以我手动实现了这个概念:在类this._setData = false 上声明一个属性。在setData里面,只运行代码if (!this._setData),在运行代码的最后,做一个this._setData = true。这意味着代码只会运行一次。至于将其推广到装饰器以应用于任何方法(而不是每次都必须手动声明 this._value),我也想知道!
  • 是的,我想我可以采用这种方法,但问题是设置数据函数只是一个例子,我不知道后面可以调用哪些函数或类似的东西。正如你在单元测试中看到的那样,有setData函数,下面是setUser,装饰器应该在任何地方都有相同的行为,但我不知道该怎么做:(
  • setData中,你可能是指this.data = newData

标签: javascript typescript


【解决方案1】:

尝试这样的事情:

const once = (
  target: Object,
  propertyKey: string | symbol,
  descriptor: PropertyDescriptor
) => {
  const method = descriptor.value;
  let isFirstTime = true;

  descriptor.value = function (...args: any[]) {
    if (!isFirstTime) { return; }
    isFirstTime = false;
    method(...args);
  }
};

【讨论】:

  • 不过,每次调用该方法时,isFirstTime 不会重新创建并设置为 true 吗?
  • @SethLutske 装饰器只在声明类时执行一次。当调用该方法时,该方法实际上是分配给descriptor.value的新函数。它不会重新运行装饰器。
  • 这似乎在this example 中给我一个错误。尝试运行代码,然后取消对 @once 的注释,然后再次运行。从 once 方法中访问 this.property 属性似乎有问题...?如果它能正常工作,代码应该运行,并打印计数为 1 的 Cell,即使该方法被调用了 100 次
  • @SethLutske 除了thisreturn 的问题之外,实际上这个答案中的缺陷与您最初的担忧相反:班级只有一个isFirstTime,而不是每个实例一个。这就是为什么我们通常使用“隐藏”实例成员,或者像 plumbn 的回答中那样使用反射元数据。
  • @ghybs,有趣。我曾尝试过类似的东西,但没有成功。正如我的 ts playground 和 codesandbox 示例所示,这个特定答案似乎不起作用。我认为隐藏的实例成员是最可读的,但是需要很多重复的逻辑。我喜欢 plumbn 的回答,但它没有解决 getters 的问题,或者当你只想第一次计算一个值,然后每隔一次简单地返回该值而不必再次计算时。我可能会写一个单独的问题来涵盖这种情况。
【解决方案2】:

reflect-metadata 对于这种情况非常方便。你可以尝试这样的事情:

import 'reflect-metadata';

const metadataKey = Symbol('initialized');

export function once(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const method = descriptor.value;
    descriptor.value = function(...args) {
        const initialized = Reflect.getMetadata(metadataKey, target, propertyKey);

        if (initialized) {
            return;
        }

        Reflect.defineMetadata(metadataKey, true, target, propertyKey);

        method.apply(this, args);
    }
}

您可以在这里找到更多信息:https://www.typescriptlang.org/docs/handbook/decorators.html#metadata

【讨论】:

  • 这似乎有效。 Here is a codesandbox 举个例子。
  • 我注意到的一件事是,这在尝试修饰 get 方法时不起作用。我收到错误 Invalid property descriptor. Cannot both specify accessors and a value or writable attributeHere's a codesandbox demonstrating that error。您是否认为可以扩展/调整它以便为 get 类方法提供相同的功能?
  • @SethLutske 我认为要装饰吸气剂,我们需要一个属性装饰器。题目问的是方法装饰器,所以我觉得现在的版本比较合适。
  • 您介意在答案中也写一个属性装饰器吗?或者,如果您愿意,我可以在一个单独的问题中提出这个问题?
  • @SethLutske 对不起,我说错了。您要求访问器装饰器。示例可以在这里找到:typescriptlang.org/docs/handbook/…
【解决方案3】:
function once(target: any, methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor {
    // Your implementation here
    const originalMethod = descriptor.value;
    let isFirstTime = true;
    descriptor.value = function (...args: any[]) {
        if (!isFirstTime) {
            return;
        }
        isFirstTime = false;
        originalMethod.apply(this, args);
    };
    return descriptor;
}

【讨论】:

    猜你喜欢
    • 2018-06-21
    • 2020-01-02
    • 2017-04-02
    • 2021-01-26
    • 2016-11-11
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 2020-01-11
    相关资源
    最近更新 更多