LoginSignup
5
1

More than 3 years have passed since last update.

[TypeScript]仕様を守らない者に地獄送りの裁きを! ~ ジハード ~ Analyticsのgtagの型を片っ端から固めてみた

Posted at

※ TypeScriptでオーバーロードを使わずに、異なる引数を持つ関数を作る疑似オーバロードは関数宣言に統合を確認してください
※ あんまり活用が進まないExcludeによる型フィルターの使い方も同じ場所にあります

それは地獄の蓋を開くきっかけだった

 現在運用中の技術情報掲載サイトをPHPからNode.js(TypeScript)に移植している途中で、フロントエンドの方もかなりの部分を作り直している。そこでAnalytics関連の組み込みを行おうとしたら、npmにgtagの適切な@typesな定義が見当たらなかった。探し方が悪いだけなのかもしれないが、仕様を確認して自分で作ってみることにした。それが地獄の蓋を開くことになるとも知らずに。

1.Analyticsの仕様を確認

 実際に必要とするパラメータは極わずかなのだが、仕様を確認してみると地獄のパラメータの多さである。情報が分散していて、結局のところの仕様を抽出するのが非常にやりにくかった。後半に関しては、もはやいらないのではとも思ったが、こうなったら意地である。

https://developers.google.com/analytics/devguides/collection/gtagjs/pages?hl=ja
https://developers.google.com/analytics/devguides/collection/gtagjs/events?hl=ja
https://developers.google.com/analytics/devguides/collection/gtagjs/screens?hl=ja
https://developers.google.com/analytics/devguides/collection/gtagjs/user-timings?hl=ja
https://developers.google.com/analytics/devguides/collection/gtagjs/exceptions?hl=ja
https://developers.google.com/gtagjs/reference/api?hl=ja
https://developers.google.com/gtagjs/reference/event?hl=ja
https://developers.google.com/gtagjs/reference/parameter?hl=ja

 基本的には以下の三種類のコマンドがある。

  • config congigには専用パラメータと、eventパラメータ全てを初期設定しておく機能がある
  • set ユーザが設定しておきたいデータで自由使用
  • event 大量の推奨パラメータとカスタム型、コイツが全ての地獄仕様の現況

 eventに関しては、定義されているパラメータが多すぎて気が狂いそうになった。こいつら全てをTypeScriptの型チェックに引っかけて、仕様から逸脱したら赤線を引っ張ってエラーにしてやるのだ!

2.仕様に基づいて、ひたすらinterface化

 TypeScriptでは、これが非常に面倒な作業である。npmに上がっているものなら話は早いのだが、無かった瞬間に地獄は始まるのだ。

config用

/**
 *config用コントロールパラメータ
 *
 * @interface ControlParameter
 */
declare interface ControlParameter {
  groups?: string | string[];
  send_to?: string | string[];
  event_callback?: () => void;
  event_timeout?: number;
}
/**
 *config用ページビューパラメータ
 *
 * @interface PageViewParameter
 */
declare interface PageViewParameter {
  send_page_view?: boolean;
  groups?: string;
  page_title?: string;
  page_location?: string;
  page_path?: string;
}

event用

/**
 *event用推奨パラメータ
 *
 * @interface DefaultEventParameter
 */
declare interface DefaultEventParameter {
  checkout_option?: string;
  checkout_step?: number;
  content_id?: string;
  content_type?: string;
  coupon?: string;
  currency?: string;
  description?: string;
  fatal?: boolean;
  items?: {
    brand: string;
    category: string;
    creative_name: string;
    creative_slot: string;
    id: string;
    location_id: string;
    name: string;
    price: number;
    quantity: string;
  }[];
  method?: string;
  name?: string;
  promotions?: {
    creative_name: string;
    creative_slot: string;
    id: string;
    name: string;
  }[];
  screen_name?: string;
  search_term?: string;
  shipping?: number;
  tax?: number;
  transaction_id?: string;
  value?: number;
  app_name?: string;
  app_id?: string;
  app_version?: string;
  app_installer_id?: string;
}

/**
 *event用推奨イベント
 *
 * @interface DefaultEvent
 */
declare interface DefaultEvent {
  add_payment_info?: undefined;
  add_to_cart?: {
    value?: DefaultEventParameter["value"];
    currency?: DefaultEventParameter["currency"];
    items?: DefaultEventParameter["items"];
  };
  add_to_wishlist?: {
    value?: DefaultEventParameter["value"];
    currency?: DefaultEventParameter["currency"];
    items?: DefaultEventParameter["items"];
  };
  begin_checkout?: {
    value?: DefaultEventParameter["value"];
    currency?: DefaultEventParameter["currency"];
    items?: DefaultEventParameter["items"];
    coupon?: DefaultEventParameter["coupon"];
  };
  checkout_progress?: {
    value?: DefaultEventParameter["value"];
    currency?: DefaultEventParameter["currency"];
    items?: DefaultEventParameter["items"];
    coupon?: DefaultEventParameter["coupon"];
    checkout_step?: DefaultEventParameter["checkout_step"];
    checkout_option?: DefaultEventParameter["checkout_option"];
  };
  exception?: {
    description?: DefaultEventParameter["description"];
    fatal?: DefaultEventParameter["fatal"];
  };
  generate_lead?: {
    value?: DefaultEventParameter["value"];
    currency?: DefaultEventParameter["currency"];
    transaction_id?: DefaultEventParameter["transaction_id"];
  };
  login?: {
    method?: DefaultEventParameter["method"];
  };
  page_view?: undefined;
  purchase?: {
    transaction_id?: DefaultEventParameter["transaction_id"];
    value?: DefaultEventParameter["value"];
    tax?: DefaultEventParameter["tax"];
    shipping?: DefaultEventParameter["shipping"];
    items?: DefaultEventParameter["items"];
    coupon?: DefaultEventParameter["coupon"];
  };
  refund?: {
    transaction_id?: DefaultEventParameter["transaction_id"];
    value?: DefaultEventParameter["value"];
    currency?: DefaultEventParameter["currency"];
    tax?: DefaultEventParameter["tax"];
    shipping?: DefaultEventParameter["shipping"];
    items?: DefaultEventParameter["items"];
  };
  remove_from_cart?: {
    value?: DefaultEventParameter["value"];
    currency?: DefaultEventParameter["currency"];
    items?: DefaultEventParameter["items"];
  };
  screen_view?: {
    screen_name?: DefaultEventParameter["screen_name"];
    app_name?: DefaultEventParameter["app_name"];
    app_id?: DefaultEventParameter["app_id"];
    app_version?: DefaultEventParameter["app_version"];
    app_installer_id?: DefaultEventParameter["app_installer_id"];
  };
  search?: {
    search_term?: DefaultEventParameter["search_term"];
  };
  select_content?: {
    items?: DefaultEventParameter["items"];
    promotions?: DefaultEventParameter["promotions"];
    content_type?: DefaultEventParameter["content_type"];
    content_id?: DefaultEventParameter["content_id"];
  };
  set_checkout_option?: {
    checkout_step?: DefaultEventParameter["checkout_step"];
    checkout_option?: DefaultEventParameter["checkout_option"];
  };
  share?: {
    method?: DefaultEventParameter["method"];
    content_type?: DefaultEventParameter["content_type"];
    content_id?: DefaultEventParameter["content_id"];
  };
  sign_up?: {
    method?: DefaultEventParameter["method"];
  };
  timing_complete?: {
    name?: DefaultEventParameter["name"];
    value?: DefaultEventParameter["value"];
  };
  view_item?: {
    items?: DefaultEventParameter["items"];
  };
  view_item_list?: {
    items?: DefaultEventParameter["items"];
  };
  view_promotion?: {
    promotions?: DefaultEventParameter["promotions"];
  };
  view_search_results?: {
    search_term?: DefaultEventParameter["search_term"];
  };
}


/**
 *event用通常パラメータ
 *
 * @interface EventParameter
 */
declare interface EventParameter {
  event_category?: string;
  event_label?: string;
  value?: number;
  non_interaction?: boolean;
}

関数宣言に統合

 引数や型が異なる関数を宣言する場合、TypeScriptではオーバロードを使うのが一般的っぽいが、実は必要ない。可変引数と共用体型を使えば、一つの関数の宣言中に全てを記述することが可能だ。

/**
 *gtag定義
 *
 * @template K
 * @param {(...["config", string, (ControlParameter|PageViewParameter|DefaultEventParameter)?]
 *     | ["set", { [key: string]: string }]
 *     | ["event", K, (DefaultEvent[K] & EventParameter)]
 *     | ["event", Exclude<string, K>, EventParameter?])} params
 */
declare function gtag<K extends keyof DefaultEvent>(
  ...params:
    | ["config", string, (ControlParameter|PageViewParameter|DefaultEventParameter)?]
    | ["set", { [key: string]: string }]
    | ["event", K, (DefaultEvent[K] & EventParameter)]
    | ["event", Exclude<string, K>, EventParameter?]
): void;

3.使ってみる

 Analyticsのサンプルで使われている以下のコードが全て通ることを確認した。もちろん関係ないパラメータを設定するとエラーになるし、数値であるべき場所に文字列を放り込むとやっぱりエラーになる。

gtag("config", "GA_MEASUREMENT_ID", {
  page_title: "homepage",
  page_path: "/home"
});
gtag("config", "GA_MEASUREMENT_ID_1");
gtag("config", "GA_MEASUREMENT_ID_2");

gtag("event", "xyz");

gtag("event", "aaa", {
  event_category: "bbb",
  event_label: "ccc"
});

gtag("event", "login", { method: "Google" });

gtag("event", "video_auto_play_start", {
  event_label: "My promotional video",
  event_category: "video_auto_play",
  non_interaction: true
});

gtag("event", "screen_view", {
  app_name: "myAppName",
  screen_name: "Home"
});
gtag('config', 'GA_MEASUREMENT_ID', { 'app_name': 'myAppName' });
gtag('event', 'screen_view', { 'screen_name': 'Home'});


gtag('event', 'timing_complete', {
  'name' : 'load',
  'value' : 3549,
  'event_category' : 'JS Dependencies'
});
gtag('event', 'exception', {
  'description': 'error_description',
  'fatal': false   
});

4.使うのこれ?

 たぶん使わない。configの一部しか使わない。event使わない。とりあえず出来たのでnpmに放り込んでおくべきだろうか?

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1