TypeScript 中类似反射的密钥到字符串的转换?

Reflection-like conversion of key to a string in TypeScript?

我用的是Proxy object with the idea that whenever a property gets updated, some other side-effect can also be initiated. I don't want to initiate the side effects in the many places that properties get set (DRY principle).

有点做作的示例代码:

const session = new Proxy(
  { id: undefined as number, age: undefined as number}, // =target
  {
    set: (target, property, value): boolean => {
      switch (property) {
          case 'id': {
            target[property] = value;
            this.notifyIdWasUpdated(value);
            return true;
          }
          case 'age': {
            target[property] = value;
            this.updateAgeDisplay(value);
            return true;
          }
          default: {
            return false;
          }
        }
    }
  }
);

我的问题是,当我使用 IDE 的重构更改 target 的 属性 名称(键)时对象(例如 age),case 语句中的字符串常量(例如 'age' 也得到更新(可能导致错误)。

问题:有没有办法从case语句中的表达式obj.key动态获取字符串值'key'(其中然后将是重构证明)? ( 某种 ['key'] 访问器相反,在某种程度上...)或者,您能否建议另一种构建上述代码的方法以防止这种程序员监督?


你可以在这里尝试使用keyof

interface Session {
  id: number
  age: number
}

const session1 = new Proxy(
  { id: 0, age: 0 } as Session,
  {
    set: (target, property: keyof Session, value): boolean => {
      switch (property) {
        case 'id': {
          target[property] = value;
          this.notifyIdWasUpdated(value);
          return true;
        }
        case 'age': {
          target[property] = value;
          this.updateAgeDisplay(value);
          return true;
        }
        default: {
          return false;
        }
      }
    }
  }
);

这不会自动重命名,但如果 case 中的 属性 在 Session 中不存在,typescript 将显示错误。

以下情况应允许自动重命名:

interface Session {
  id: number
  age: number
}

type Handlers<Model> = {
  [Key in keyof Model]: (newValue: Model[Key]) => void;
}

// Partial<Handlers<Session>> in case you don't want to handle each property
const handlers: Handlers<Session> = {
  id: () => { },
  age: () => { },
}

const session = new Proxy(
  { id: 0, age: 0 } as Session,
  {
    set: (target, property: keyof Session, value): boolean => {
      const handler = handlers[property];

      if (handler) {
        handler(value)

        return true;
      }

      return false;
    }
  }
);

简单的解决方案是这样的:

function nameof<TType>(selector: (t?: TType) => any) {
  const match = selector
    .toString()
    .match(/=>\s*(?:[a-zA-Z0-9_$]+\.?)*?([a-zA-Z0-9_$]+)$/);

  if (match) {
    return match[1];
  }

  return undefined;
}

interface MyType {
  id: any;
  age: number;
}

const session = new Proxy(
  { id: undefined as number, age: undefined as number }, // =target
  {
    set: (target, property, value): boolean => {
      switch (property) {
        case nameof<MyType>((t) => t.id): {
          target[property] = value;
          this.notifyIdWasUpdated(value);
          return true;
        }
        case nameof<MyType>((t) => t.age): {
          target[property] = value;
          this.updateAgeDisplay(value);
          return true;
        }
        default: {
          return false;
        }
      }
    },
  }
);

DEMO

注意:小心,如果你的目标是 ES5!箭头函数被转译为带有 return 的常规函数​​,因此正则表达式将不起作用,您必须更改正则表达式。

虽然选择了另一个 ,但给定示例代码的问题在更高的抽象级别上。由于session对象封装了很多属性,所以session对象应该作为一个单元来处理,而不是属性分开处理。 (可能有一个代码气味名称或其他一些针对此的警告...)

样本将只是:

session = new Proxy(
  { id: undefined, age: undefined}, // =target
  {
    set: (target, property, value): boolean => {
        if (typeof property === 'string' && Object.keys(target).includes(<string>property)) {
          target[property] = value;
          doSideEffects(target);
          return true;
        } else {
          return false;
        }
      },
    }
  );

这简化了代理中的处理程序。

(我是 OP。在我的例子中,它现在也大大简化了副作用代码。我想橡皮鸭效应开始发挥作用了...)