どっちを使うべき?TypeScriptのtype(型エイリアス)とinterfaceの違いと使い分け

この記事では、type
(型エイリアス)と interface
の違いと、それぞれの使い方、そしてどっちを使うべき?について解説いたします。
機能対応表
type
(型エイリアス)と interface
がサポートする機能の可否を一覧にしています。
# | 機能 | type | interface | 説明 |
---|---|---|---|---|
1 | 宣言のマージ | 不可 | 可能 | 同名宣言を重ねると自動で統合されるかどうか |
2 | 拡張(extends / & ) | 制約あり(& で合成) | 可能 | 派生型を作成する手段の違い |
3 | プリミティブ型エイリアス | 可能 | 不可 | type ID = string のように別名を付けられるか |
4 | ユニオン型 | 可能 | 不可 | A | B を直接定義できるか |
5 | タプル型 | 可能 | 制約あり | [number, string] を自然に書けるか |
6 | 関数型 | 可能 | 可能 | (arg: T) => U の形を宣言できるか |
7 | オブジェクト型 | 可能 | 可能 | プロパティを持つ構造体を定義できるか |
8 | リテラル型 | 可能 | 不可 | 文字列/数値リテラルを直接型にできるか |
9 | 条件型 | 可能 | 不可 | A extends B ? X : Y を書けるか |
10 | 型アサーション | 可能 | 不可 | as const など型計算を伴うアサーションに使えるか |
11 | infer キーワード | 可能 | 不可 | 条件型で部分型を抽出できるか |
12 | typeof 演算子 | 可能 | 不可 | 実値から型を取得できるか |
13 | keyof 演算子 | 可能 | 不可 | プロパティ名を列挙したユニオンを生成できるか |
14 | インデックスシグネチャ | 可能 | 可能 | [K: string]: V を宣言できるか |
15 | マッピング型 | 可能 | 不可 | [K in Keys]: T で型を変換できるか |
16 | テンプレートリテラル型 | 可能 | 不可 | ${Uppercase<T>}Id のような型計算が可能か |
17 | 再帰型 | 可能 | 可能 | 自分自身を参照する型を定義できるか |
18 | クラスによる implements | 不可 | 可能 | class Foo implements Bar で利用できるか |
19 | 名前空間とのマージ | 不可 | 可能 | namespace と同名で拡張できるか |
20 | コールシグネチャのオーバーロード | 可能 | 制約あり | 同一型で複数の関数シグネチャを持てるか |
基本形の定義
1. 宣言のマージ(Declaration Merging)
解説
同じ名前の interface
を複数回書くと自動で結合されます。type
で同じことを行うと重複エラーになります。
ts// interface はマージされる
interface ApiContext { reqId: string }
interface ApiContext { user?: { id: number } }
const ctx: ApiContext = { reqId: 'abc', user: { id: 1 } };
// type はエラー
// type ApiContext = { reqId: string }
// type ApiContext = { user?: { id: number } } // ✅ 上と同名なので NG
2. 拡張(extends / &)
解説
interface
は extends
、type
は交差型 &
で派生を作成します。
tsinterface Animal { name: string }
interface Dog extends Animal { bark(): void }
type Position = { x: number }
type Position3D = Position & { z: number }
3. プリミティブ型エイリアス
解説
ID などのドメイン概念を表すときは type
が便利です。interface
では書けません。
tstype UserId = string | number;
const id: UserId = 42;
4. ユニオン型
解説
type
でしかユニオンを直接宣言できません。interface
は要素側としてのみ利用します。
tstype Status = 'draft' | 'published' | 'archived';
interface Draft { status: 'draft' }
interface Published { status: 'published' }
type ArticleState = Draft | Published;
5. タプル型
解説
type
なら [number, string]
を自然に定義できます。interface
で表す場合は添字シグネチャが必要です。
ts// 推奨
type Pair = [number, string];
// interface で無理やり書く例
interface PairLike {
0: number;
1: string;
length: 2;
}
6. オブジェクト型
解説
どちらもサポートされますが、マージ前提なら interface
、型演算前提なら type
が向きます。
tsinterface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
7. 関数型
解説
シグネチャだけなら type
が短く、オーバーロードを多用する場合は interface
が読みやすいです。
ts// type
type ToString = (value: unknown) => string;
// interface(オーバーロード)
interface Parse {
(s: string): number;
(n: number): string;
}
8. リテラル型
解説
固定値の表現は type
で行います。interface
では直接書けません。
tstype Yes = 'yes';
type No = 'no';
9. 条件型
解説
ジェネリックに応じて型を切り替える強力な仕組みで、type
限定です。
tstype Nullable<T> = T extends null | undefined ? never : T;
type A = Nullable<string | null>; // string
10. 型アサーション(as const 等)
解説
type
を組み合わせ、リテラルを保持したまま厳密型に変換できます。
tsconst config = {
mode: 'production',
port: 3000,
} as const;
type Config = typeof config; // { readonly mode: "production"; readonly port: 3000 }
11. infer キーワード
解説
条件型内で部分型を抽出できます。複雑な戻り値推論に便利です。
tstype ReturnTypeOf<T> = T extends (...args: any) => infer R ? R : never;
function foo() { return { ok: true } }
type R = ReturnTypeOf<typeof foo>; // { ok: boolean }
12. typeof 演算子
解説
実値から型を取得して再利用します。
tsconst defaultUser = { id: 0, name: 'guest' };
type DefaultUser = typeof defaultUser;
13. keyof 演算子
解説
オブジェクトのプロパティ名をユニオンで取り出します。
tsinterface User { id: number; name: string }
type UserKeys = keyof User; // "id" | "name"
14. インデックスシグネチャ
解説
動的キーを持つ辞書構造を表現します。
tsinterface Dictionary {
[key: string]: string;
}
type NumRecord = { [key: number]: boolean };
15. マッピング型
解説
既存キーを走査して新しい型を作成します。type
限定です。
tstype Optional<T> = { [K in keyof T]?: T[K] };
interface User { id: number; name: string }
type PartialUser = Optional<User>; // 全プロパティがオプション
16. テンプレートリテラル型
解説
文字列リテラルを組み合わせて新しい型を生成します。
tstype EventName<E extends string> = `${E}Changed`;
type Domain = 'user' | 'post';
type Events = EventName<Domain>; // "userChanged" | "postChanged"
17. 再帰型
解説
自己参照によりツリー構造を安全に記述できます。
tsinterface TreeNode<T> {
value: T;
children?: TreeNode<T>[];
}
type Json = string | number | boolean | null | Json[] | { [k: string]: Json };
18. クラスによる implements
解説
クラスが契約を満たすかコンパイル時に検証できます。interface
専用です。
tsinterface Runnable { run(): void }
class Task implements Runnable {
run() { console.log('running'); }
}
19. 名前空間とのマージ
解説
ライブラリ拡張時に namespace
と interface
を同名で定義し、静的メソッドを追加できます。
tsinterface Greeter { greet(): void }
namespace Greeter {
export function create(): Greeter {
return { greet() { console.log('hi'); } };
}
}
const g = Greeter.create();
g.greet();
20. コールシグネチャのオーバーロード
解説
複数シグネチャを一つの型で表現します。type
なら簡潔、interface
はプロパティと混在できますが書き方がやや冗長です。
ts// type 版
type Overloaded =
| ((n: number) => string)
| ((s: string) => number);
// interface 版(制約あり)
interface Fn {
(n: number): string;
(s: string): number;
}
const f: Fn = (arg: any) =>
typeof arg === 'number' ? String(arg) : arg.length;
どっちを使うべき?
結論を先に述べます。拡張性・互換性を重視するなら interface
、型演算と柔軟な構成力を求めるなら type
が最適です。以下ではメリット/デメリットを整理し、代表的な利用パターンごとにおすすめの選択肢を示します。
メリット/デメリット一覧
# | 観点 | type の特徴 | interface の特徴 |
---|---|---|---|
1 | 拡張性 | 宣言のマージ不可。合成は交差型 & で明示 | 宣言のマージと extends で段階的に拡張しやすい |
2 | 表現力 | ユニオン・条件型・テンプレートリテラル型など高度な型演算が可能 | 構造宣言に特化。リテラル・条件型など直接記述は不可 |
3 | IDE 補完 | 型計算が多いと補完候補が長くなる傾向 | オブジェクト構造を宣言するため補完が直感的 |
4 | ライブラリ互換 | 外部公開では breaking change を誘発しやすい | 再宣言マージで後方互換を保ちやすい |
5 | 学習コスト | 条件型や infer を理解する必要がある | Java Interface に近く初学者に理解しやすい |
パターン別おすすめ
# | シーン | 推奨 | 理由 |
---|---|---|---|
1 | 公開 SDK・型定義ファイル | interface | ユーザー側で拡張できるため将来互換が保たれる |
2 | 内部ユーティリティ関数群 | type | 条件型・テンプレートリテラル型で汎用型を生成しやすい |
3 | REST API レスポンス DTO | interface | バージョンごとの追加項目を宣言マージで吸収できる |
4 | Redux Action など列挙的ユニオン | type | 複数リテラルを ` |
5 | ドメイン ID や定数の別名 | type | プリミティブ別名を簡潔に定義できる |
6 | プラグインアーキテクチャ | interface + type | 外部契約を interface 、オプション型を type で表現 |
7 | JSX Props 型(拡張前提) | interface | extends で派生コンポーネントに対応しやすい |
8 | 再帰ツリーや JSON 型 | type | 再帰型と条件型を組み合わせた表現が容易 |
運用のヒント
- ライブラリや共有コードでは「まず
interface
で契約を定義し、必要に応じてtype
で派生型を組み立てる」方針が安全です。- アプリ内部ロジックでは
type
を主役にし、ユニオン・条件型で型安全と可読性を高めると保守が楽になります。- 型選択はファイル単位で統一せず、機能ごとに最適な方を柔軟に混在させると設計が洗練されます。
まとめ
最初に押さえるべき核心は 「拡張性なら interface、型演算なら type」 というシンプルな方針です。
宣言のマージや extends
による段階的拡張が必要な公開 API では interface
が安全であり、ユニオン・条件型・テンプレートリテラル型など高度な表現力を活かしたい内部ユーティリティでは type
が威力を発揮します。
実装面では次の三点を意識すると設計が安定します。
-
契約を先に定義し拡張は後から加える
外部向けの DTO やプラグイン API はinterface
で宣言し、互換性を維持したまま追加できる形にする。 -
型演算は専用のユーティリティ型に集約する
type
の条件型やinfer
を使い回し、実ロジック側をシンプルに保つことで可読性を高める。 -
IDE 補完とコンパイルエラーの質を常に評価する
interface
の直感的な補完とtype
の強力な型ガードを使い分け、開発者体験を最適化する。
最後に、チームルールを「宣言マージ=interface」「型演算=type」と明確に分けるだけでも保守コストは大幅に削減されます。
ぜひ活用してみてください。
関連リンク
TypeScriptの記事Typescript
- article
TypeScript 5.8 で強化された型推論!その裏で潜む 落とし穴と回避策
- article
【早見表】TypeScript Generics(ジェネリクス)の使用例と記法まとめ
- article
【2025年5月版 早見表】TypeScript 5.7 tsconfig.jsonの主要オプションのまとめ
- article
VSCodeで開発時のインポート補完(TypeScript)を相対パスからエイリアスにする設定
- article
【解決策】TypeScriptで発生するTS2564エラーの対応
- article
Next.js のTypeScriptプロジェクトへeslint、stylelint、prittierを導入してVSCodeで自動フォーマットするまでの手順
- article
TypeScript 5.8 で強化された型推論!その裏で潜む 落とし穴と回避策
- article
【早見表】TypeScript Generics(ジェネリクス)の使用例と記法まとめ
- article
開発AIエディタ比較 Github Copilot vs Cursor vs Cline vs devin!それぞれの特徴や料金の違いを比較してみた
- article
【2025年5月版 早見表】TypeScript 5.7 tsconfig.jsonの主要オプションのまとめ
- article
【対処法】Cursorで発生する「Connection failed. If the problem persists ...」エラーの原因と対応
- article
Next.jsとEdge Runtimeを組み合わせて超爆速サーバーレス表示を実現する方法