TypeScript で window 直下にいろいろ生やしたりグローバル変数を定義する

TypeScript でブラウザー上で動作させるコードを書く場合に、グローバル変数 window を拡張する方法のまとめ
2019.12.20

TypeScript は開発に安定をもたらしてくれますが、たまにやりたいことがちょちょっとできずにハマることがあります。今日はそのひとつ、 window オブジェクトにいろいろ生やしたいんだけどうまく生やせないあなたのための記事です。

ポイントは次のふたつです。

  1. tsconfig.json の lib"DOM" が指定されているかどうか
  2. window の定義において import / export を使っているかどうか

tsconfig.json の lib"DOM" が指定されているかどうか

tsconfig.json の lib プロパティに "DOM" が指定されているかどうかで書くべき内容が変わります。ご自身の tsconfig.json の中身を確認してみてください。 lib プロパティがない場合は "DOM" が指定されているものとして扱ってください

DOM あり

この場合、 interface のマージを使います。 lib"DOM" が指定されている場合、 DOM に関する各種の定義が読み込まれるため、 Window 型および、この型を持つ ambient 変数 window が存在するためです。

DOM なし

こちらでは ambient 変数 window を定義する必要があります。素のままでは何も定義が存在しないからですね。

windowの定義においてimport / export を使っているかどうか

さてもうひとつの判断軸です。どちらかを使っている場合、 TypeScript はその定義があるファイルを module として認識します。複数の module で同名の定義があってもエラーとならないよう、それぞれの module は名前空間を持ちます。拡張するときはこの名前空間を指定してやらなければならないのですね。

module として定義する場合

window は名前空間 global を持って定義されますので、 declare global を使って合わせてやる必要があります。

module として定義しない場合

この場合、特に考慮すべきことはありません。

パターン別実例

次のコードをコンパイルするために必要な定義例を、パターンごとに紹介します。

function main() {
  return window.myProp
}

main()

また、最低限の tsconfig.json は次になります。libはパターンによって変更してください。

{
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
    "baseUrl": "./",
    "paths": {
      "*": ["./src/@types/*"]
    }
  }
}

パターンは次の 4 つです。

DOM なし DOM あり
module でない 1 3
module である 2 4

window の定義は src/@types/window.d.ts に書きましょう。

パターン 1

interface Window {
  myProp: number
}
declare var window: Window

declare var で ambient な変数を宣言しているだけですね。

パターン 2

import MyProp from './my-prop'

interface Window {
  myProp: number
}

declare global {
  var window: Window
}

module となっているため、 window が定義されている名前空間 global 以下に ambient 変数を宣言してください。

パターン 3

interface Window {
  myProp: number
}

interface を拡張する書き方です。既存の windowmyProp が追加されます。もちろん scrollToaddEventListner など window が持っているべき API も使用可能です。

パターン 4

import MyProp from './my-prop'

declare global {
  interface Window {
    myProp: MyProp
  }
}

こちらは名前空間をあわせてやっているだけで、パターン 3 と同様です。

注意点

パターン 1 およびパターン 2 について、 window が持っているべき API を使いたい場合は自分で定義してあげましょう。こういった定義はあまり頻度が高くないと思いますが、 window 以外のグローバル変数を宣言する際も同様ですので参考にしてください。

まとめ

できるだけ ambient 宣言は使わないほうが良いと思うのですが、 window についてはしょうがないところがあるのでご自身のパターンに最適な定義を使ってください。