TypeScript で ValueObject を作る時の問題点
thisは宣言されたclassを返す
多くは共通の関数が存在しますので、基底クラスを作成して、継承することを考えると思います。ですが、typescriptのthisは宣言されたclassを返します。
なので、 abstract
にするのが良いと思います。 abstract
にしたclassは初期化を禁止されます。
また、typescriptでファクトリーを作成するときstaticを採用することでしょう。
その際、function句を使用することでthisを定義することができます。
export abstract class ValueObject<T> {
constructor(protected value: T) {}
public get() {
return this.value;
}
public eq<Instance extends ValueObject<T>>(
this: Instance,
valueObject: Instance
): boolean {
return valueObject.get() === this.value;
}
public static of = function <T, Instance extends ValueObject<T>>(
this: new (value: T) => Instance,
value: ReturnType<Instance['get']>
) {
return new this(value);
};
}
typescriptはメンバーが同じなら同等に評価する
基底クラスを使用して、価格と金額を作成して、矛盾した代入をしてみると、lintは何も反応してくれません。これは初期化した後にオブジェクトの構造が同じだから起こります。
理想はエラーを出してほしい!
import { ValueObject } from './ValueObject';
/**
* 価格
*/
class Price extends ValueObject<number> {}
/**
* 金額
*/
class Amount extends ValueObject<number> {}
const amount: Amount = Price.of(100);
解決 エラーを出してもらう
この記事では、 unique symbol
を使用する事で、この問題を解決します。
下記のコードは強引ですが、目的を確実に果たしてくれます。
説明すると、 private
にすることで露出させず、class内部で参照がないことでlintがエラーを検出するため、 @ts-ignore
を指定して、無視することをしています。Uniqueは再利用可能ですので、テンプレートとしても利用可能です。
import { ValueObject } from './ValueObject';
declare const Unique: unique symbol;
/**
* 価格
*/
export class Price extends ValueObject<number> {
// @ts-ignore
private [Unique]: void;
}
ビルド後に残る?
private [Unique]: void;
の部分は定義のみですので、コードには残りません。
下記ビルド後のコード
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ValueObject_1 = require("./ValueObject");
class Price extends ValueObject_1.ValueObject {}
exports.Price = Price;
Reactのプロジェクトでの話
ReferenceError: Unique is not defined
index.html
に Unique
変数を定義することで回避できる
<script>
var Unique = undefined;
</script>
メンバーが残ってる
整合性を取るか、無駄をなくすかのトレードオフ。。。
undefined: undefined
value: 100
__proto__: ValueObject