blog.waterlow.work

Ruby, Rails, js, etc...

TypeScriptで関数のthisの型注釈をする

はじめに

vueを使ってTypeScriptに入門しているときに以下の問題にあたった。

forum.vuejs.org stackoverflow.com

そもそもthisの型注釈の機能を知らなかったので、公式ドキュメントを読みながらthisの型注釈についてインプットすることにした。

  • 型注釈によって関数のthisの型を指定することができる
  • thisの型注釈が必要なケース

型注釈によって関数のthisの型を指定することができる

Functions · TypeScript

以下のようなコードを考える。

class C1 {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  say() {
    console.log(this.name)
  }
}

say メソッドの中で呼ばれているthis の型はthisになり、C1 のサブクラスでも辻褄が合うような扱いになる。 ただし、以下のような形でthis の型を明示することができる。

class C1 {
  // ...
  say(this: C1) {
    console.log(this.name)
  }
}

C1 を指定した場合は挙動は問題なさそうだが、void などを指定するとコンパイルエラーになる。 なぜこのような機能があるのか。

thisの型注釈が必要なケース

noImplicitThisを有効にしている場合に必要になることがある。 以下のようなコードを考える。

const execCallback = (fn: () => void) => {
  fn()
}

class C1 {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  say() {
    execCallback(function() { console.log(this.name) })
  }
}

say メソッドの中で console.log(this.name) を呼ぶ関数を execCallback に渡しているが、関数式で定義した関数のthisは実行時に決まるため、sayメソッド内で見ているthisはany型になってしまう。結果としてnoImplicitThisに引っかかるのである。警告を外す方法としてはsayメソッドの中のfunction() { ... }function(this: any) { ... }とすれば警告を解除できるが、残念ながら実行時に落ちてしまう。。。結局 function は使わず。アロー関数を使うのが今回の正解だ。

ただしアロー関数を使えないケースもある。「はじめに」の中で書いたvueのmethod内で _.debounce を使う例がわかりやすい。(この例もアロー関数を使えるのかもしれない。) vueのforumにもあるように、このケースは妥協してanyを使うのも良さそうだ。

まとめ

TypeScriptに入門していたところ早速本に出ていない内容が出てきたが、ちゃんとvueのforumやstackoverflow、そして何より公式ドキュメントにthisの型注釈について書いてあったのが良かった。