🏛️
TypeScriptでClassの初期化をもっと楽にする
はじめに
TypeScriptでClassを定義する場合、みなさんはどのように初期化を行っていますか?
コンストラクタに引数を並べる方法が一般的だと思いますが、引数が多い場合や、引数の順番を間違えるとバグの原因になります。
class User {
public readonly id: number;
public readonly firstName: string;
public readonly lastName: string;
public constructor(id: number, firstName: string, lastName: string) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
}
const user = new User(1, 'Taro', 'Yamada');
下記のように、引数をオブジェクトにまとめる方法もありますね。
class User {
public readonly id: number;
public readonly firstName: string;
public readonly lastName: string;
public constructor(user: User) {
this.id = user.id;
this.firstName = user.firstName;
this.lastName = user.lastName;
}
}
const user = new User({ id: 1, firstName: 'Taro', lastName: 'Yamada' });
しかし、この方法だとメソッドがある場合に型エラーが発生します。
class User {
public readonly id: number;
public readonly firstName: string;
public readonly lastName: string;
public constructor(user: User) {
this.id = user.id;
this.firstName = user.firstName;
this.lastName = user.lastName;
}
public fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
const user = new User({ id: 1, firstName: 'Taro', lastName: 'Yamada' });
// Property 'fullName' is missing in type
この記事では、これらの問題を解決しつつ、Classの初期化をもっと楽にする方法を紹介します。
動的Class定義を活用する
JavaScriptでは、下記のように関数内部でClassを定義して返すことができます。
const factory = () => {
abstract class BaseClass {
}
return BaseClass;
}
class SubClass extends factory() {
}
この特性を利用したのが、下記のStruct
関数です。
const Struct = <Properties extends Record<string, unknown>>(): new(
properties: Properties,
) => Readonly<Properties> => {
abstract class Class {
protected constructor(properties: Properties) {
Object.assign(this, properties);
}
}
return Class as any;
};
Struct
関数は、Genericsにプロパティを受け取り、そのプロパティを持つClassを返す関数です。
Constructorは、引数で受け取ったプロパティをObject.assign
で自身にコピーします。
使い方
Struct
関数を使うと、下記のようにClassを定義できます。
class User extends Struct<{
id: number;
firstName: string;
lastName: string;
}>() {
public fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
const user = new User({ id: 1, firstName: 'Taro', lastName: 'Yamada' });
User
Classはメソッドを持っていますが、型エラーは発生せず、初期化も簡単に行えます。
プロパティの割り当ては、Struct
関数が生成するClassが行ってくれるため、Constructorを定義する必要がありません。
まとめ
ModelやDTOを定義する際には、今回紹介したStruct
関数を使ってみてください。
引数の順番を気にする必要がなくなり、Classの定義も楽に行えます。
Discussion