JavaScript
TypeScript

【TypeScript】 Object[key]() (ブラケット記法)で関数呼び出ししたら Element implicitly has an 'any' type でハマった話

More than 1 year has passed since last update.

【問題】ブラケット表記法でメソッド呼び出しするとコンパイルが通らない。

静的クラスに実装したメソッドをブラケット表記法で呼び出します。

const Klazz = {
  doHoge() {
    console.log('done hoge');
  },
  doFuga() {
    console.log('done fuga');
  },
  doPiyo() {
    console.log('done piyo');
  }
};

const key: string = 'doFuga';
Klazz.doFuga[key]();  // Element implicitly has an 'any' type because type '() => void' has no index signature.

Element implicitly has an 'any' type because type '() => void' has no index signature.
'()=> void'型にはインデックスの署名がないため、要素は暗黙的に 'any'型です。

【原因】静的クラスの keyany 型であるため。

下記のふたつは同じ意味

Klazz.doFuga();     // ドット表記法
Klazz['doFuga']();  // ブラケット表記法

JavaScript では Obeject[key]() でメソッドを呼ぶ際に key を変数にすると動的にメソッドを呼び出すことができます。
しかし、TypeScript では key を変数にするとコンパイラに怒られます。

const key = 'doHoge';
Klazz[key]() エラー!

Klazz['doHoge'] とリテラルを直接埋め込む場合はコンパイルが通ります。)

【対策】 interface を定義してメソッドを定義するオブジェクトに実装する。

interface IKlazz {
  [key: string]: Function;  // ←シグネチャー
  doHoge(): void;
  doFuga(): void;
  doPiyo(): void;
}

const Klazz: IKlazz = {
  doHoge() {
    console.log('done hoge');
  },
  doFuga() {
    console.log('done fuga');
  },
  doPiyo() {
    console.log('done piyo');
  }
};

const key: string = 'doFuga';
Klazz[key]();  // done fuga

interface を定義することでコンパイラに型を知らせます。
シグネチャ [key: string]: Function; には keystring 、戻り値が関数と定義します。
なぜ、シグネチャの戻り値が void でなく Function であるかはトランスパイルされた JavaScript を見るとわかります。

トランスパイルされたJavaScript
var Klazz = {
    doHoge: function () {
        console.log('done hoge');
    },
    doFuga: function () {
        console.log('done fuga');
    },
    doPiyo: function () {
        console.log('done piyo');
    }
};

トランスパイルされたコードでは key :文字、 value匿名関数の連想配列になっています。

【サンプル】静的クラスに登録されたメソッドを順に動的に呼び出す。

interface IKlazz {
  [key: string]: Function;  // シグネチャ― 
  doHoge(): void;
  doFuga(): void;
  doPiyo(): void;
}

/* 状態を持たせたくないので不変オブジェクトとして生成しました。 */
const Klazz: IKlazz = Object.freeze({
  doHoge() {
    console.log('done hoge');
  },
  doFuga() {
    console.log('done fuga');
  },
  doPiyo() {
    console.log('done piyo');
  }
});

const myKeys: string[] = ['doFuga', 'doPiyo'];

Object.keys(Klazz).forEach(key => {
  const isMyKey = myKeys.some(myKey => myKey === key);
  if (isMyKey) {
     Klazz[key]();  // done fuga, done piyo
  }
});

object.png

inteface を実装したのでブラケット表記法で key を変数にしてもコンパイルが通ります。
Object.keys()key の配列を返すため Object.keys(Klazz).forEach(key => Klazz[key]());Klazz のメソッドをすべて実行できます。
上記の例ではメソッドを動的に呼び出す例のため、配列 myKeys に格納された key 名のメソッドのみ実行しました。

参考にしたウェブページ