Google Analyticsのgtag型定義で迷走

[Qiita Advent Calendar 2021](https://qiita.com/advent-calendar/2021)
[タイムリープTypeScript 〜TypeScript始めたてのあの頃に知っておきたかったこと〜](https://qiita.com/advent-calendar/2021/timeleap-typescript)
4日目の記事として
あの頃に知っていれば、こんなに遠回りしなかったgtag設定のエピソードをご紹介します。

1. [さいしょに](#さいしょに)
1. [解決方法](#解決方法)
1. [初心者のあゆみ](#初心者のあゆみ)
1. [さいごに](#さいごに)

# さいしょに

```typescript
window.gtag('config', 測定ID);
```

TypeScriptのプロジェクトで、このようにGoogle Analyticsの測定のみ記述すると
`window.gtag`の型が定義されていないことを指摘するエラーになります。

このエラーから正解にたどり着くまでを、当時をふりかえってまとめてみました。


# 解決方法

:::note info
gtagの型定義が公開されているので、インストールすればよい。
:::

[@types/gtag.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/gtag.js)をインストールすると、`window.gtag`が型定義済みの状態になります。

```bash
# npmのプロジェクトへインストールする場合
npm install @types/gtag.js

# yarnのプロジェクトへインストールする場合
yarn add -D @types/gtag.js
```


# 初心者のあゆみ
こじらせポイントと、全体のながれをふりかえり
初心者がさまよった様子をみていきます。

## こじらせポイント
２つの要素がこじらせて、解決まで時間がかかりました。

:innocent:
[Type Search](https://www.typescriptlang.org/dt/search?search=gtag)の存在に気づいていなくて、型定義ファイルを見つけるコツがわかっていなかった

:innocent:
`tsconfig.json`の理解が浅いまま`types`を設定していたため
@types/gtag.jsをインストールしただけでグローバルに使用できなかった。


`tsconfig.json`には、`gtag.js`とは別のパッケージを導入するタイミングで設定した`types`がありました。

```json:tsconfig.jsonのtypes
{
  "compilerOptions": {
    "types": ["hoge"]
  },
}
```


## 当時をふりかえる
おこった出来事、やったことを、順番にふりかえります。

1. 型定義がないのでエラー
1. `window`の子要素へ型を追加できる？
1. 苦肉のany回避
1. 型定義をインストールしたけど反応がない…

### 型定義がないのでエラー
型定義のエラーを予感しつつ、とりあえず`gtag`を使うと
やっぱりエラーでした。

```
Property 'gtag' does not exist on type 'Window & typeof globalThis'.  TS2339
```
:::note alert
gtagはWindowの型にもTypeScriptのglobalThisの型にも存在しません
:::

そうですよね。

<FONT COLOR=darkgray>:innocent: この時点でType Searchへアクセスし型定義を検索したら、もっと解決が早かった。</FONT>

### `window`の子要素へ型を追加できる？
とはいえ、自分がつくった変数に型を設定できるのは知っているけれど
`window`にぶら下がっていても型って設定できるもの？

エラーの文言でそのまま検索すると、windowの子要素にも型を設定できることがわかりました。

https://r17n.page/2019/11/21/fix-typescript-window-object-access-error/

なるほど、`declare global`というのを設定すると、`window`の子供でも型がつけられた！

```typescript
declare global {
  interface Window {
    gtag: any;
  }
}
```

`any`がESLintにたしなめられていて気持ち悪いけど、エラーが解けて動くようになりました。

### 苦肉のany回避
`any`のままは気持ち悪いけど、gtag.jsの型定義を手作りする根性もなかったので
もう少し検索してみました。

すると、`gtag`のイベンを発火させる関数をクッションにするやり方を知りました。

[htmlへgtagを設定](https://developers.google.com/analytics/devguides/collection/app-web?hl=ja)するついでに、発火させる自作の関数`pageViewEvent`も設定し
`declare`では`pageViewEvent`の型を設定して`any`回避しました。

```html:htmlファイルのheaderに発火用のpegeViewEvent設置
<script async src="https://www.googletagmanager.com/gtag/js?id=測定ID"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() {
    dataLayer.push(arguments);
  }
  gtag('js', new Date());
  // gtagページビュー送信イベントを実行
  function pageViewEvent(){
    window.gtag('config', 測定ID);
  }
</script>
```

```ts:発火イベントpageViewEventの型
declare global {
  interface Window {
    pageViewEvent: () => void;
  }
}
```

```ts:declareしているのでセーフ
window.pageViewEvent();
```

しかし、エラーもないし`any`も使わない形にできたけど
ほんとうの意味で型を定義したわけではないのでは？


### 型定義をインストールしたけど反応がない…

実はこの時点で、
公開されているgtagの型定義があり、それ導入している解説記事を見つけていました。

しかし、手順にしたがって`npm install`しても
`window.gtag`が型定義されている状態とならず、行き詰まっていました。

（ここで、しばらく諦めの期間に突入…）

`pegeViewEvent`で騙し騙し動かしつつ、手がかりを検索していると
@types/gtag.jsが効いていなかった原因がみつかりました！

https://stackoverflow.com/questions/62306920/gtag-function-typescript-definition


> ただし、`tsconfig.json`で`types`が既に設定されている場合はグローバルに検出されません。
`types`を取り除くか、`gtag.js`を`types`に追加しましょう。
(Will Squireさんのアドバイス意訳)

`tsconfig.json`が影響してたんか…
[こじらせポイント](#こじらせポイント)にあったよう、gtagを設定しようとしていたプロジェクトでは
`types`にhogeが設定されている状態でした。

<FONT COLOR=darkgray>:innocent: typesを設定したとき、意味や影響を深堀りせずお手本どおりにしていた。</FONT>

```json:tsconfig.json
{
  "compilerOptions": {
    "types": ["hoge", "gtag.js"]
  },
}
```
こうして、導入の解説記事ではインストールするだけで型定義できていたのに
自分のプロジェクトでは型定義できなかった原因がわかり、全てが解決しました。

# さいごに

この記事を書くために、当時あがいたキーワードで思い出し検索したのですが
あのときよりも多くの記事がみつかり
TypeScriptの盛り上がり、というか、もう定着してきている情勢を感じました。

まだまだ習熟度が浅いので、今後ともがんばって勉強します。