LoginSignup
0
1

More than 5 years have passed since last update.

TypeScriptのPropertyDecoratorの使い方を理解するためにオレオレDIを作ってみる

Posted at

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

書いてみましょう。

DIContainer.ts
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的なことができそうです。
以上です。よろしくお願いいたします。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1