kanejaku.org

TypeScriptの型チェックと仲良くする

Publish date 6 Feb 2019

TypeScript のコンパイルエラーを一時的に抑止したい場面は多々ある。この記事では、自分が型エラーを回避するのに便利だなと思っている機能を状況に応じて 3 つ紹介したいと思う。想定している文脈は、趣味プロジェクトで、フレームワークを使わない素のフロントエンド開発。

Type Guards

状況: このエレメントは<foo>なんだからbarっていう属性があるのに TypsScript はそれを分かってくれない。

例えばオーディオを再生するページを静的に記述したとする。

<audio id="my-audio"></audio>

このオーディオに対して再生位置をリセットするスクリプトを書きたい。書いている側からするとmy-audioHTMLAudioElementであることが分かっている。getElementById('my-audio')の返り値はHTMLAudioElementだからと思って以下のように書くとコンパイラに怒られる。

const audioEl = document.getElementById("my-audio");
// NG: `[ts] Property 'currentTime' does not exist on type 'HTMLElement'. [2339]`
audioEl.currentTime = 0;

この場合は Type guards に頼る。if 文で型の整合性をチェックすると、コンパイラがコントロールフローを解析して型を限定してくれる。以下では if 文以降audioElHTMLMediaElementであることが保証される。

const audioEl = document.getElementById("my-audio");
if (!(audioEl instanceof HTMLMediaElement)) {
  throw new Error("#my-audio is not an HTMLMediaElement");
}
// OK: At this point TS compiler knows `audioEl` is an HTMLMediaElement.
audioEl.currentTime = 0;

冗長だけど if 文以降に型チェックの恩恵を受けられることを考えるとトレードオフとしては悪くない。asを使う方法もあるけれど、 Type Guards を使ったほうがより安全になる。

参考: Advanced Types · TypeScript

Non Null Assertion Operator

状況: 関数foo()nullundefinedじゃない値を返すのが分かってるのに TypeScript それを分かってくれない。

2D の絵を描きたいとする。ブラウザ上で 2D の絵を描くにはHTMLCanvasElementを用意してそれに対してgetContext('2d')を呼んで描画コンテキストを取得する。だけど TypeScript はgetContext()nullを返すかもしれないと文句を言ってくる。

// NG: [ts] Type 'CanvasRenderingContext2D | null' is not assignable to type 'CanvasRenderingContext2D'.
//          Type 'null' is not assignable to type 'CanvasRenderingContext2D'. [2322]
const ctx: CanvasRenderingContext2D = canvas.getContext("2d");

この場合は!を末尾につけている。これはNon Null Assertion Operatorというやつで、式の末尾に!をつけるとコンパイラはその式がnullundefinedを返すことがないと仮定するようになる。nullundefinedを返す式にしか使えないけど、Type guards を使うよりも簡潔に型を限定できる。

// OK
`const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
ctx.clearRect(0, 0, width, height);
// An amazing drawing follows

ただ Type guards と違ってコンパイラが null や undefined にならないことを保証してくれるわけでは無い、という点に注意。

参考: TypeScript 2.0 · TypeScript

@ts-ignore

状況: window に一時的にデバッグ用のプロパティを追加したいけど TypeScript がそれを許してくれない。

開発の初期段階ではブラウザのデベロッパーツールを使っていろんな検証をしたい。例えば自作のAppオブジェクトの状態をデベロッパーツールで確認したいとする。そんなときには手っ取り早くアクセスできるオブジェクト、例えばwindowにそのオブジェクトをぶら下げるのが簡単だろう。でも単純にそれをやろうとするとコンパイラが怒る。

const app = new App(...);
// NG: [ts] Property 'app' does not exist on type 'Window'. [2339]
window.app = app;

こういう状況では@ts-ignoreを使っている。@ts-ignoreをコメントとして書くと、以後の一文だけはコンパイラは何もエラーを出さなくなる。tsconfig.json などで一括にエラーを抑止するのは避けたいけど、この一文だけ見逃して欲しい場面で重宝する。

const app = new App(...);
// @ts-ignore
window.app = app; // OK
...
console.log(window.app); // NG

あくまで@ts-ignoreの直下の行だけエラーを出力しないようになるだけなので、ほかの場所でappを使おうとするとコンパイラに怒られる。自分は@ts-ignoreをDevToolsを使ったデバッグや調査をしたいときや、トリッキーなimportをしている場所のエラーを抑止したいときなんかに使っている。

参考: TypeScript 2.6 · TypeScript

参考文献

Revised Revised 型の国のTypeScript は非常に良い入門書。一日ぐらいかけて目を通しておくと TypeScript の気持ちがわかるようになると思う。自分は技術書典3で紙の本を購入した。

TypeScriptのunsafeな操作まとめ では TypeScript の型検査が常に有効ではないことを議論している。