Decorator?
Decoratorとは、Javaのアノテーションのようなものです。
なお、DecoratorはTypeScriptでは既に利用可能ですが、JavaScriptでは未だドラフトのようです。
オレオレDIコンテナを作ってみる
PropertyDecoratorの使い方を知るためにも、試しにDIコンテナを作ってみましょう。
書きっぷりを見てみる。
まずは、どう書けるといい感じなのか、試してみましょう。
class Provider {
feature() {
console.log("called method");
}
}
class User {
@inject(Provider)
provider!: Provider
callFeature() {
this.provider.feature();
}
}
const provider = new Provider();
register(provider).as(Provider);
const user = new User();
user.callFeature();
ポイントとしては、@inject(Provider)
で何を注入するのか指定するところと、register().as()
で注入するインスタンスを登録しているところです。
JavaのCDIでは、魔法のようにリフレクションでどうにかなっているようですが、TypeScriptやJavaScriptでは難しそうだと判断し、そのへんはちょっと目をつぶることにしました。
また、手動でインスタンスを指定できるのは、テストを書くときにも役立つでしょう。
実装してみる
実現に必要なのは以下のふたつです。
- 関数:
register().as()
- Decorator:
inject
書いてみましょう。
const map: Map<any, any> = new Map();
function register(instance: any) {
return {
as(specifier: any) {
map.set();
}
}
}
function inject(specifier: any): PropertyDecorator {
const instance = map.get(specifier);
return function(target: any, propertyKey) {
target[propertyKey] = instance;
}
}
書きました。
map
という変数にインスタンスを管理させます。
inject
は、PropertyDecorator
という型の関数を返します。
PropertyDecorator
は実際にプロパティをいじくる役割の関数です。
target
には@inject
が指定されたクラスがまるっと渡ってきます。
propertyKey
にはプロパティ名が渡ってくるので、二つの変数をもとに、プロパティにインスタンスを突っ込むようにしています。
動作確認
試しに、結合してみましょう。
this.provider.feature();
^
TypeError: Cannot read property 'feature' of undefined
むむ、注入できてないようですね。なぜ?
デバッグ
得意のprintf
デバッグで原因を探ってみたところ、そもそもinject
が呼び出されるタイミングが早すぎることがわかりました。
register
より前なのです。
クラスの定義が行われるタイミングで、inject
が呼び出されています。
register
後はきちんとアクセスできるようにしたい
function inject(specifier: any): PropertyDecorator {
return function(target: any, propertyKey) {
Object.defineProperty(target, propertyKey, {
get() {
return map.get(specifier);
}
})
}
}
プロパティをgetterにすることで、後からアクセスされたときにきちんとインスタンスにアクセスできるようになりました。
ほかのところは同じです。
動きました。
テストでも使ってみる
jestで書くとこんな感じでしょうか。
describe("User", () => {
const provider = { feature: jest.fn() };
beforeEach(() => {
register(provider).as(Provider);
});
describe("#callFeature", () => {
it("should call provider feature.", () => {
const user = new User();
user.callFeature();
expect(provider.feature).toBeCalledTimes(1);
});
});
});
ということで、Decoratorを使えば簡単にDI的なことができそうです。
以上です。よろしくお願いいたします。