1

我想写一个消毒剂装饰器,我可以把它放在所有用户输入的字符串字段上。这只是将标准替换.set(newValue).set( sanitize(newValue) ). 但是我发现下面的代码只适用于一个实例。同一类的第二个实例最终共享 currentValue。在进一步阅读之后,这实际上是预期的,但我无法弄清楚如何在每个实例中制作它。

import "reflect-metadata";

export const Sanitize = () => {
    return (target: any, propertyKey: string | symbol) => {
        let currentValue: any = sanitiseString(options, `${target[propertyKey] || ''}`);

        Reflect.deleteProperty(target, propertyKey);

        Reflect.defineProperty(target, propertyKey, {
            get: () => currentValue,
            set: (newValue: string) => {
                currentValue = sanitiseString(newValue);
            },
        });
    }
}

编辑 1:最小可重现示例:

import "reflect-metadata";

const sanitiseString = (valToSanitise: string) => {
  // do some stuff, return clean value
  return valToSanitise;
}

const Sanitize = () => {
  return (target: any, propertyKey: string | symbol) => {
    let currentValue: any = sanitiseString(`${target[propertyKey] || ''}`);

    Reflect.deleteProperty(target, propertyKey);

    Reflect.defineProperty(target, propertyKey, {
      get: () => currentValue,
      set: (newValue: string) => {
        currentValue = sanitiseString(newValue);
      },
    });
  }
}

class UserInput {
  constructor(propOne: string, propTwo: string, propThree: number) {
    this.propOne = propOne;
    this.propTwo = propTwo;
    this.propThree = propThree;
  }

  @Sanitize() propOne: string
  @Sanitize() propTwo: string
  propThree: number
}

const inputOne = new UserInput('input 1, prop 1', 'input 1, prop 2', 1)
const inputTwo = new UserInput('input 2, prop 1', 'input 2, prop 2', 2)

console.log(inputOne)
console.log(inputTwo)

// expected output: 
// [LOG]: UserInput: {
//    "propOne": "input 1, prop 1",
//    "propTwo": "input 1, prop 2",
//    "propThree": 1
// } 
// [LOG]: UserInput: {
//    "propOne": "input 2, prop 1",
//    "propTwo": "input 2, prop 2",
//    "propThree": 2
// } 
//  
// actual output: 
//
// [LOG]: UserInput: {
//    "propThree": 1
// } 
// [LOG]: UserInput: {
//    "propThree": 2
// } 
// When you remove @Sanitize() the fields appear in console.log. When you add @Sanitize() the fields disappear.
// Further, forcing console.log(inputOne.propOne) returns [LOG]: "input 2, prop 1" 
// indicating that the property is being written for the class proto and not per instance

console.log(inputOne.propOne)
4

1 回答 1

0

这里的主要问题是Sanitize()每个装饰类属性声明都会调用一次,因此对于任何给定的类属性,只有一个currentValue. 这意味着该类的两个实例将共享相同的currentValue. 如果您想为每个类实例的每个装饰类属性存储一个值,那么您需要访问类实例,并且您必须将值存储在这些实例中(通过不会干扰任何其他实例的属性键)属性),或者在某些以这些实例为键的地图中。下面我将展示如何将值存储在类实例中,并且为了避免担心属性名称冲突,我将使用函数的输出symbol它保证是唯一的。Symbol

另请注意,将类原型作为参数Sanitize()传递,因此您执行的任何操作都会影响原型,而不是类的任何实例。当你写的时候,你是在类原型中查找属性,而-valued 属性几乎肯定不会在原型中设置。所以这可能没有必要或没有用,我们应该摆脱它。targettargettarget[propertyKey]string

因此,如果您只能直接访问类原型,您将如何处理类实例?好吧,要做到这一点,您应该使用您传递给的访问器属性描述符的方法方法this的上下文。这意味着并且需要是方法或至少是表达式,而不是没有明确上下文的箭头函数表达式。getsetdefineProperty()getsetfunction this


好了,解释够了,代码如下:

const Sanitize = () => {
  return (target: any, propertyKey: string | symbol) => {
    const privatePropKey = Symbol();
    Reflect.defineProperty(target, propertyKey, {
      get(this: any) {
        return this[privatePropKey]
      },
      set(this: any, newValue: string) {
        this[privatePropKey] = sanitiseString(newValue);
      },
    });
  }
}

让我们确保它按您的预期工作。让我们sanitiseString实际做一些事情:

const sanitiseString = (valToSanitise: string) => {
  return valToSanitise+"!"; 
}

现在对于我们的班级:

class UserInput {
  constructor(propOne: string, propTwo: string, propThree: number) {
    this.propOne = propOne;
    this.propTwo = propTwo;
    this.propThree = propThree;
  }
  @Sanitize() propOne: string
  @Sanitize() propTwo: string
  propThree: number
}

最后让我们看看它是否有效:

const inputOne = new UserInput('input 1, prop 1', 'input 1, prop 2', 1)
const inputTwo = new UserInput('input 2, prop 1', 'input 2, prop 2', 2)

console.log(inputOne.propOne, inputOne.propTwo, inputOne.propThree)
console.log(inputTwo.propOne, inputTwo.propTwo, inputTwo.propThree);
// GOOD OUTPUT
// [LOG]: "input 1, prop 1!",  "input 1, prop 2!",  1 
// [LOG]: "input 2, prop 1!",  "input 2, prop 2!",  2 

看起来不错。每个实例UserInput都有自己的净化propOnepropTwo属性。

Playground 代码链接

于 2022-03-03T03:34:59.903 回答