Back

TypeScriptプロジェクトにおける型定義の整理方法

TypeScriptプロジェクトにおける型定義の整理方法

ほとんどのTypeScriptプロジェクトは同じように始まります。単一のtypes.tsファイルが徐々に数百行に膨れ上がっていきます。何かを見つけるのが面倒になり、ファイル間で型を再利用するのがリスクに感じられ、新しいチームメンバーはどこを見ればいいのか分からなくなります。

良いニュースは、TypeScriptの型を整理するのに複雑なシステムは必要ないということです。必要なのは1つの明確なルールです:型は使用される場所にできるだけ近く配置し、他の場所で必要になった時だけ移動する。

以下、プロジェクトのあらゆる段階でこのルールを適用する方法を説明します。

重要なポイント

  • 型は使用される場所にできるだけ近く配置し、複数のファイルで必要になった時だけ共有の場所に昇格させる。
  • 明示的な型のエクスポートとインポートには.tsファイルを使用し、.d.tsファイルは環境変数やグローバル拡張などのアンビエント宣言専用に予約する。
  • モジュール固有の型には*.types.ts、クリーンで安定したインポートパスのためのバレルファイルにはindex.tsといった一貫した命名規則を採用する。
  • モノレポや共有パッケージでは、package.jsonのexportsにおけるtypes条件を通じて型を公開し、内部型は非公開に保つ。

核となる判断:型はどこに配置すべきか?

自分自身に1つの質問をしてください:この型を必要とするファイルはいくつあるか?

  • 1つのファイル → そのファイル内でインラインで定義する
  • 同じモジュール内の数個のファイル → 近くの共有.types.tsファイルに配置する
  • プロジェクト全体 → 共有のsrc/types/ディレクトリに移動する
  • 複数のパッケージ → 専用の型パッケージから公開する

この段階的なアプローチにより、最初から過度に設計することなく、プロジェクトの型定義を整理された状態に保つことができます。

パターン1:ローカル使用のためのインライン型

型が1つのファイルでのみ使用される場合は、そこで定義します。別ファイルは不要です。

// UserCard.tsx
interface UserCardProps {
  name: string
  avatarUrl: string
  role: "admin" | "viewer"
}

export function UserCard({ name, avatarUrl, role }: UserCardProps) {
  // ...
}

これが正しいデフォルトです。時期尚早な抽象化は、利益なしに間接性を生み出すだけです。

パターン2:共有モジュール型のための配置型ファイル

同じフォルダ内の2つ以上のコンポーネントが型を共有する場合、それらを配置された.types.tsファイルに抽出します。

src/components/user/
├── UserCard.tsx
├── UserList.tsx
└── user.types.ts       ← このモジュールの共有型

これにより、グローバルファイルを汚染することなく、関連する型をそれを使用するコードの近くに保つことができます。

パターン3:横断的な定義のための共有types/ディレクトリ

型が複数の無関係なモジュール間で使用される場合、中央のsrc/types/ディレクトリが適切です。バレルindex.tsを使用して、インポートをクリーンで安定した状態に保ちます。

src/types/
├── index.ts            ← すべてを再エクスポート
├── api.types.ts
├── user.types.ts
└── product.types.ts
// src/types/index.ts
export type { ApiResponse, PaginatedResult } from "./api.types"
export type { User, UserRole } from "./user.types"

利用者は1つのパスからインポートします:import type { User } from "@/types"。後で内部を再編成しても、インポートパスは変更されません。

型定義には.ts.d.tsのどちらを使うべきか?

これはTypeScriptの型整理において最も一般的な混乱ポイントの1つです。

.tsファイルを使用するのは、明示的にエクスポートおよびインポートする型の場合です。これはアプリケーションのほぼすべてにおいて正しい選択です。

.d.tsファイルを使用するのは主にアンビエント宣言の場合です — 実行時に存在するがTypeScriptソースを持たない何かについてTypeScriptに伝える必要がある状況です。一般的な例:

  • env.d.ts — Viteまたは類似のバンドラーのためのimport.meta.env変数の宣言
  • global.d.ts — サードパーティモジュール型の拡張またはグローバル変数の宣言
// env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
  readonly VITE_API_URL: string
}

一部の最新プロジェクトでは、どのアンビエント型がロードされるかを制御するためにcompilerOptions.typesを明示的に設定していますが、他のプロジェクトは自動的な@types検出に依存しています。ViteやNext.jsのようなバンドラーベースのツールチェーンを使用している場合は、独立したグローバル宣言を作成するのではなく、これらのファイルに対する推奨規則に従ってください。

スケールする命名規則

一貫した命名は、チーム全体の認知負荷を軽減します:

パターン使用例
*.types.tsuser.types.tsモジュール固有の型
index.tstypes/index.tsバレル再エクスポート
env.d.tsenv.d.ts環境変数の宣言
global.d.tsglobal.d.tsサードパーティ型の拡張

IUserプレフィックスやUserTypeサフィックスは避けてください。UserApiResponseのようなシンプルな名前の方がクリーンで、現代のTypeScript規則に沿っています。

共有パッケージにおけるTypeScript型の整理

ライブラリを構築している場合やモノレポで作業している場合は、古いtypesVersionsパターンに依存するのではなく、types条件を使用してpackage.jsonのexportsを通じて型を公開します。

{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  }
}

内部型は非公開に保ちます — パッケージのエントリーポイントからエクスポートしないでください。公開APIサーフェスのみが利用者に見えるべきです。

まとめ

型はインラインから始めます。2つ目のファイルが必要になったら配置ファイルに抽出します。本当に横断的になったらsrc/types/に昇格させます。.d.tsファイルはアンビエント宣言のためだけに使用し、すべての型定義のデフォルトの置き場所としては使用しません。

TypeScript型を整理する目的は、完璧なフォルダ構造ではありません — 次の開発者(または将来の自分)が摩擦なく型を見つけて再利用できるようにすることです。

よくある質問

いいえ。単一ファイルは初期段階では機能しますが、プロジェクトが成長するにつれてナビゲートが困難になります。代わりに、型は使用される場所の近くに保ちます。単一ファイルでの使用にはインラインで定義し、モジュール内で共有される場合は配置された.types.tsファイルに抽出し、コードベースの無関係な部分間で必要になった場合のみ中央の型ディレクトリに昇格させます。

アプリケーションコードで明示的にエクスポートおよびインポートする型には.tsファイルを使用します。.d.tsファイルは、環境変数の記述やサードパーティモジュール型の拡張などのアンビエント宣言のためだけに使用します。通常のアプリケーション型を.d.tsファイルに配置すると、明示的なインポートなしでそれらの宣言がグローバルに利用可能になるため、混乱を引き起こす可能性があります。

広く採用されている規則は、モジュール固有の型にはmodule-name.types.tsパターンを使用し、共有型ディレクトリでの再エクスポートにはindex.tsバレルファイルを使用することです。IUserのようなハンガリアン記法やUserTypeのような冗長なサフィックスは避けてください。UserやApiResponseのようなシンプルで説明的な名前が、現代のTypeScriptプロジェクトでは好まれます。

package.jsonのexportsフィールド内のtypes条件を使用して、コンパイルされた宣言ファイルを指定します。このアプローチは古いtypesVersionsパターンよりも明示的で信頼性があります。公開APIの一部を形成する型のみをエクスポートし、実装の詳細が利用者に漏れないように内部型は非公開に保ちます。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay