LoginSignup
2
3

More than 5 years have passed since last update.

[TypeScript] ジェネリックで引数で渡されたインスタンスのメソッドを使いたい

Last updated at Posted at 2018-03-21

想定するケース

  • HogeService というクラスがある
  • HogeService のメソッド: exec() は他のクラスのインスタンスを引数で受け取る
  • HogeService は exec() において、引数で渡されたインスタンスのメソッドを実行する
  • ただし HogeService のメソッドに引数でセットされるインスタンスのクラスは固定ではない

つまり次のようなケースを想定する。

  • HogeService

    HogeService
    export class HogeService {
      public exec(instance: any) {
        instance.exec();
      }
    }
    
  • 引数で渡されるインスタンスのクラス-1 ( Bar1Service )

    Bar1Service
    export class Bar1Service {
      public exec() {
        console.log('Bar1Service-exec()');
      }
    }
    
  • 引数で渡されるインスタンスのクラス-2 ( Bar2Service )

    Bar2Service
    export class Bar2Service {
      public exec() {
        console.log('Bar2Service-exec()');
      }
    }
    

HogeService のインスタンスメソッド: exec() は Bar1Service、もしくは Bar2Service のインスタンスを受け取る。
このとき HogeService が受け取るインスタンスの型を any ではなく ジェネリック で受け取りたいのだが、単純に HogeService のインスタンスメソッドで

  • 単純なジェネリクスの指定

    HogeService
    export class HogeService {
      public exec<T>(instance: T) {
        instance.exec();
      }
    }
    

とするだけでは

  • 文法エラー

    エラー
    [ts] プロパティ 'exec' は型 'T' に存在しません。
    

が出てしまう。
本記事ではこのエラーが出ずにジェネリックでインスタンスを受け取り、そのインスタンスのメソッドを使用する方法について提示する。

解決方法

解決方法は以下のとおり。

  • 引数として渡されるクラス
    • 抽象クラスを用意する
    • 抽象クラスを継承する
  • インスタンスを受け取る側のクラス
    • メソッド定義時、 ジェネリックを抽象クラスを拡張した形式で指定する

これでジェネリックでインスタンスを受け取り、そのインスタンスのメソッドの実行を実現できる。
以下、実際にコードを記述する。

引数として渡されるクラス

抽象クラスを用意する

AbstractBar
export abstract class AbstractBarService {
  public abstract exec();
}

抽象クラスを継承する

Bar1Service
import { Injectable } from '@angular/core';
import { AbstractBarService } from './abstract-bar.service';

@Injectable()
export class Bar1Service extends AbstractBarService{

  constructor() {
    super();
  }

  public exec() {
    console.log('Bar1Service-exec()');
  }
}
Bar2Service
import { Injectable } from '@angular/core';
import { AbstractBarService } from './abstract-bar.service';

@Injectable()
export class Bar2Service extends AbstractBarService {

  constructor() {
    super();
  }

  public exec() {
    console.log('Bar2Service-exec()');
  }
}

インスタンスを受け取る側のクラス

メソッド定義時、ジェネリックを抽象クラスを拡張した形式で指定する

HogeService
import { Injectable } from '@angular/core';
import { AbstractBarService } from './abstract-bar.service';

@Injectable()
export class HogeService {

  constructor() { }

  public exec<T extends AbstractBarService>(instance: T) {
    instance.exec();
  }
}

まとめ

以上で掲題の「TypeScript のジェネリックで、引数で渡されたインスタンスのメソッドを使いたい」が実現できた。
本文の繰り返しとなるが、ポイントは

  • 引数となるクラス側
    • 抽象クラスを用意する
  • 引数を受け取るクラス側
    • メソッド定義時、ジェネリックを抽象クラスを拡張した形式で指定する

である。
これで TypeScript による型定義の恩恵を受けられるし、コード補間機能 ( Visual Studio Code で確認 ) の恩恵も受けられる。

ソースコード

今回の記事で作成したコードは こちら にアップしてあるのでご参考まで。

捕捉

@pCYSl5EDgo 様からのコメントより転記

ジェネリックと abstract を用いずに 想定するケース に対する回答を @pCYSl5EDgo 様から頂きましたので、コードを転記いたします。

ジェネリックとabstractを用いない回答
export class HogeService {
  public exec(instance: {exec:()=>any}) {
    instance.exec();
  }
}

確かにシンプルでいいですね。これで上記までの実装と同じ結果が得られます。
ご指摘にもあるとおり、ジェネリックと abstract を用いるのはやり過ぎなケースにおいて有用な対応だと思います。

@pCYSl5EDgo 様

ご指摘、ありがとうございました。

2
3
2

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
2
3