1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptの型宣言ファイル設計パターン:グローバル宣言とモジュール宣言の共存

Posted at

はじめに

TypeScriptで大規模なWebアプリケーションを開発していると、さまざまな種類の型宣言が必要になります。特に次のような場面でよく悩みます:

  • Pugなどのテンプレートファイルのインポート
  • Windowオブジェクトへのカスタムプロパティの追加
  • グローバル関数や変数の型付け

これらの型宣言を一つのファイルにまとめようとすると、予期せぬ問題が発生することがあります。この記事では、型宣言ファイルの設計パターンと、発生しがちな問題の解決方法について解説します。

問題の背景

以下のような型宣言を一つのファイルに記述したくなるシナリオを考えてみましょう:

// 外部ファイルの型宣言
declare module '*.pug'

// グローバル変数やWindowオブジェクトの拡張
declare global {
  const someGlobalVariable: any;
  
  interface Window {
    customProperty?: any;
    analytics?: {
      trackEvent: (event: Event, params: Array<[string, string]>) => void
    }
  }
}

// モジュールとして認識させるための空のエクスポート
export {}

このようなファイルを作成すると、一見すべてが機能するように思えますが、以下のような問題が発生することがあります:

  1. Pugファイルのインポートができない
  2. グローバルWindowオブジェクトの拡張が適用されない
  3. どちらかは機能するが、両方は機能しない

問題の原因

TypeScriptの型宣言ファイルには、アンビエント宣言モジュール宣言の2つの動作モードがあります。

アンビエント宣言ファイル

export {}を含まないファイルは、アンビエント宣言ファイルとして扱われます。このファイル内のすべての宣言はグローバルスコープに適用されます。

// アンビエント宣言ファイル
declare module '*.pug' // グローバルに適用される

モジュールファイル

export {}を含むファイルはモジュールファイルとして扱われます。このファイル内の宣言はデフォルトでモジュールスコープに閉じ込められます。

// モジュールファイル
declare module '*.pug' // モジュールスコープに閉じ込められる可能性がある

declare global {
  // グローバルスコープに宣言を追加するための特別な構文
}

export {} // このファイルをモジュールとして扱うよう指示

重要なポイント

  1. declare globalブロックはモジュールファイル内でのみ有効です(export {}がある場合)
  2. アンビエント宣言ファイルではdeclare globalは不要(すでにグローバルスコープだから)
  3. export {}を追加すると、ファイル内の他の宣言の適用範囲が変わる可能性があります

解決策:宣言ファイルの分離

この問題を解決する最も確実な方法は、宣言ファイルを役割ごとに分離することです:

1. モジュール宣言用ファイル

// 外部ファイルの型宣言用
declare module '*.pug'

このファイルはアンビエント宣言ファイルとして機能し、export {}を含みません。

2. グローバル宣言用ファイル

// グローバル宣言用
declare global {
  const someGlobalVariable: any;
  
  interface Window {
    customProperty?: any;
    analytics?: {
      trackEvent: (event: Event, params: Array<[string, string]>) => void
    }
  }
}

// モジュールとして認識させるための空のエクスポート
export {}

このファイルはモジュールファイルとして機能し、export {}を含みます。

実装のポイント

安全なWindowオブジェクト拡張

Windowオブジェクトにカスタムプロパティを追加する場合は、オプショナル(?)にすることを推奨します:

interface Window {
  analytics?: {
    trackEvent: (event: Event, params: Array<[string, string]>) => void
  }
}

そして、実際にアクセスする際は存在チェックを行います:

function sendAnalytics(event: Event, name: string): void {
  if (typeof window === 'undefined') return;
  if (typeof window.analytics === 'undefined') return;
  
  window.analytics.trackEvent(event, [['eventName', name]]);
}

TypeScriptの型宣言ファイルに関するドキュメント

TypeScriptの型宣言ファイルについて詳しく知りたい場合は、以下の公式ドキュメントが参考になります:

  1. Declaration Files
  2. Declaration Merging
  3. Modules

まとめ

TypeScriptの型宣言ファイルを設計する際は、以下の点に注意しましょう:

  1. 役割ごとにファイルを分離する

    • 外部ファイル型宣言用(.pugなど)
    • グローバル宣言用(Windowインターフェースなど)
  2. ファイルの種類を意識する

    • アンビエント宣言ファイル(export {}なし)
    • モジュールファイル(export {}あり)
  3. 適切な型宣言の場所を選ぶ

    • declare moduleはアンビエント宣言ファイルに
    • declare globalはモジュールファイルに

この設計パターンに従うことで、型宣言ファイルに関する多くの問題を未然に防ぐことができます。

参考

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?