typescript:自动为构造函数的所有命名参数创建 getter

typescript: automatically create getters for all of a constructor's named parameters

为了使构造函数调用更具可读性,我通常更喜欢使用如下命名参数:

class MyClass {
  constructor(private args: {a: number, b: string, c: boolean}) {}
}

// Example constructor call:
const x = new MyClass({a: 1, b: "hello", c: true})

但是,我还希望 abc 可公开阅读,即我希望能够调用 x.ax.bx.c 而不是使用 x.args.ax.args.bx.args.c。我可以想出几种不同的方法来做到这一点(见下文),但它们都涉及重复样板来显式创建属性或吸气剂。

有没有一种方法可以自动创建这些吸气剂,例如通过编写装饰器或使用泛型?

方法 1:显式声明属性并在构造函数中填充它们

这太冗长了!

class MyClass1 {
  a: number;
  b: string;
  c: boolean;
  
  constructor(private args: {a: number, b: string, c: boolean}) { 
    this.a = args.a;
    this.b = args.b; 
    this.c = args.c;
  }
}

方法 2:存储 args 并写入 getters

比方法 1 更简洁,但我希望自动创建这些 getter。

class MyClass2 {
  
  constructor(private args: {a: number, b: string, c: boolean}) {};

  get a(): number {return this.args.a}
  get b(): string {return this.args.b}
  get c(): boolean {return this.args.c}
}

这可以使用装饰器实现您想要的效果:

// Decorator setup

interface IndexedObject { [name: string]: any }
type Constructor = new(...args: any[]) => IndexedObject

function namedConstructorArgs <T extends Constructor> (targetConstructor: T): T {
  return class proxyConstructor extends targetConstructor {
    constructor(...args: any[]){
      super(args)
      Object.entries(args[0]).forEach(
        eachNamedArg => this[ eachNamedArg[0] ] = eachNamedArg[1]
      )
    }
  }
}

// In main program

abstract class MyClassLoggerProperties {
  a: string
  b: number
}

@namedConstructorArgs
class MyClassLogger extends MyClassLoggerProperties {

  constructor(_: MyClassLoggerProperties) {
    super()
  }

  log() {
    console.log(this.a, this.b)
  }

}

new MyClassLogger( { a: "i like flake number", b: 99 } )
  .log() // logs: I like flake number, 99

请注意,我必须关闭 strictPropertyInitialization 编译器标志,以便编译器接受未在其自己的构造函数中初始化的 class 的属性。如果您希望保留编译器标志,也可以将属性初始化为虚拟值。

抽象class用于确保class语句和构造函数中的class属性相同,否则编译器不会检查它们是否一致.您可以在这些地方重复属性声明并省略构造函数中的 super() 调用,但它不会是类型安全的。

非常感谢 Simon Jacobs 和随后的评论线程,这似乎是最简洁的方法:

// type containing the properties (not methods) of a type T
type PropertiesFilter<T> = {
  [K in keyof T as T[K] extends ((...args: any) => any) ? never : K]: T[K]
}

abstract class NamedConstructorArgsBaseClass<T> {

  // Constructor takes an object with all the 
  // properties (not methods) of T
  constructor(args: PropertiesFilter<T>) {
    // Set all of those properties on the object being constructed
    Object.entries(args).forEach(
      eachNamedArg => (this as any)[eachNamedArg[0]] = eachNamedArg[1]
    )
  }

}

// in main program

class MyClassLogger extends NamedConstructorArgsBaseClass<MyClassLogger> {
  a: string;
  b: number;

  log() { 
    console.log(this.a, this.b);
  }
}

new MyClassLogger({a: "hi", b: 99}).log() // logs: hi, 99