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})
但是,我还希望 a
、b
和 c
可公开阅读,即我希望能够调用 x.a
、x.b
和 x.c
而不是使用 x.args.a
、x.args.b
和 x.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
为了使构造函数调用更具可读性,我通常更喜欢使用如下命名参数:
class MyClass {
constructor(private args: {a: number, b: string, c: boolean}) {}
}
// Example constructor call:
const x = new MyClass({a: 1, b: "hello", c: true})
但是,我还希望 a
、b
和 c
可公开阅读,即我希望能够调用 x.a
、x.b
和 x.c
而不是使用 x.args.a
、x.args.b
和 x.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