node.js - Typescript 属性装饰器可以修改实例成员而不是整个类吗?

标签 node.js typescript typescript-decorator reflect-metadata

我想编写一个 sanitizer 装饰器,可以将其放在所有用户输入字符串字段上。这只是将标准 .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)

最佳答案

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

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

因此,如果您只能直接访问类原型(prototype),那么如何对类实例执行任何操作呢?那么,要执行此操作,您应该使用 get methodthis 上下文。和 set method传递给 defineProperty() 的访问器属性描述符。这意味着 getset 需要是 methods或至少function expressions ,并且arrow function expressions没有明显的 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 link to code

关于node.js - Typescript 属性装饰器可以修改实例成员而不是整个类吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71314708/

相关文章:

reactjs - 找不到模块 : Error: Can't resolve 'react-select' with typescript

angular - 如何使用装饰器使对象的 JSON.stringify 输出 getter ?

node.js/expressjs简单文件上传问题

javascript - 在 Node.js 和 Express 应用程序中重写 URL

node.js - NPM 脚本 - 配置变量和命令替换在 package.json 中不起作用

javascript - 我可以在装饰器方法中获取变量的名称吗?

node.js - TypeScript:使用类型化装饰器函数装饰派生类

javascript - 使用 gulp 索引 Nodejs 或 browserify 组件

angular - 错误 NG6001 : Cannot declare 'Highchar tsChartComponent' in an NgModule as it's not a part of the current compilation

javascript - any[ ] 和 any[ ] = [ ] 之间的区别