mcpField
T
type-vs-interface

type-vs-interface

github.com/marius-townhouse開発645 回インストール38 週間前に更新
リポジトリ

type-vs-interface について

型の宣言や合成で `type` と `interface` を選ぶ方法を解説するTypeScriptスキルです。オブジェクト型、ユニオン、タプル、拡張可能なAPIを扱う開発者向けに判断基準を示します。

主な機能

  1. 01型の形と用途に応じた選択
  2. 02拡張可能なオブジェクトにinterface
  3. 03ユニオンやタプルに型エイリアス
  4. 04宣言マージの理解
  5. 05拡張や交差型による合成

活用シーン

  1. 01公開APIのオブジェクト型定義
  2. 02ユニオン型やタプルデータの表現
  3. 03ライブラリ型の宣言方法の選択
  4. 04コードベース内の型選択の統一

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill type-vs-interface

インストール後、Claude Code で/type-vs-interfaceを入力して読み込まれたか確認します。

詳細

typeとinterfaceの違いを知る

概要

オブジェクト型にはinterfaceを、それ以外にはtypeを使います。

typeとinterfaceはどちらもオブジェクト型を定義できますが、それぞれ異なる機能があります。こうした違いを理解しておくと、適切な方を選び、一貫性のあるコードを書けます。

このスキルを使う場面

  • 名前付きの型を定義するとき
  • 型エイリアスとインターフェースのどちらを使うか選ぶとき
  • 型を拡張したり合成したりするとき
  • 宣言ファイルを扱うとき
  • ライブラリの型定義を理解するとき

絶対に守るルール

Use INTERFACE for object types that could be extended.
Use TYPE for unions, tuples, mapped types, and complex compositions.

覚えておきましょう:

  • インターフェースは宣言マージをサポートします
  • 型エイリアスはより複雑な型を表現できます
  • どちらも拡張および実装できます
  • コードベース全体で一貫性を保つことが最も重要です

簡単な選択ガイド

シナリオ使用するもの
オブジェクト型(APIレスポンス、props)interface
ユニオン型type
タプル型type
関数型type
マップ型type
プリミティブ型のエイリアスtype
拡張を想定したライブラリの型interface

共通点

どちらもオブジェクトの形状を定義できる
type TState = {
  name: string;
  capital: string;
};

interface IState {
  name: string;
  capital: string;
}
// These are interchangeable for most purposes
どちらもジェネリクスをサポートする
type TBox<T> = { value: T };
interface IBox<T> { value: T }
どちらも拡張できる
// Interface extending type
interface IStateWithPop extends TState {
  population: number;
}

// Type extending interface
type TStateWithPop = IState & { population: number };
どちらも実装できる
class StateImpl implements IState {
  name = '';
  capital = '';
}

違い

1. ユニオン型(typeのみ)
type StringOrNumber = string | number;  // Only with type
type Status = 'pending' | 'fulfilled' | 'rejected';

// Can't do this with interface
2. タプル型(typeのみ)
type Pair = [number, number];
type NamedNums = [string, ...number[]];

// Interface syntax is awkward
interface IPair {
  0: number;
  1: number;
  length: 2;
}
3. 宣言マージ(interfaceのみ)
interface User {
  name: string;
}

interface User {
  email: string;
}

// Now User has both name and email
const user: User = { name: 'Alice', email: '[email protected]' };

これは、TypeScriptがESのバージョン間で標準ライブラリの型を拡張する仕組みです。

4. よりわかりやすいエラーメッセージ(interface)
interface Person {
  name: string;
  age: string;  // Note: string
}

// Type intersection silently creates unusable type
type TPerson = Person & { age: number };  // No error, but age is never

// Interface extension gives helpful error
interface IPerson extends Person {
  age: number;
  // ~~~ Types of property 'age' are incompatible
}
5. 型エイリアスのインライン化

TypeScriptは、エラーメッセージや.d.tsファイル内で型エイリアスをインライン化することがあります。

// In generated .d.ts, type aliases may be expanded
type Point = { x: number; y: number };
export function getOrigin(): Point { ... }

// May become:
export function getOrigin(): { x: number; y: number };

// Interfaces are preserved by name
interface IPoint { x: number; y: number }
export function getOrigin(): IPoint { ... }

// Stays as:
export function getOrigin(): IPoint;

interfaceを使う場面

公開APIを定義する
// Users might want to extend this
export interface RequestOptions {
  url: string;
  method?: string;
  headers?: Record<string, string>;
}

// Declaration merging allows extension
declare module 'my-lib' {
  interface RequestOptions {
    timeout?: number;  // User adds this
  }
}
一般的なオブジェクト型
interface User {
  id: string;
  name: string;
  email: string;
}

interface Post {
  id: string;
  title: string;
  author: User;
}

typeを使う場面

ユニオン型
type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
type Status = 'loading' | 'success' | 'error';
タプルと配列
type Point = [x: number, y: number];
type RGB = [number, number, number];
関数型
type Handler = (event: Event) => void;
type AsyncFn<T> = () => Promise<T>;
マップ型と条件型
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type NonNullable<T> = T extends null | undefined ? never : T;
計算型
type Keys = keyof User;  // 'id' | 'name' | 'email'
type UserValues = User[keyof User];  // string

拡張パターン

interfaceをinterfaceで拡張する
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}
typeをtypeと交差させる
type Animal = { name: string };
type Dog = Animal & { breed: string };
組み合わせ(どちらの方法でも可能)
// Interface extends type
interface Dog extends Animal { breed: string; }

// Type extends interface
type Cat = Animal & { meows: boolean };

圧力に負けないためのプロトコル

1. 「どちらかを選んで、それを使い続ければいい」

圧力:「選択よりも一貫性のほうが重要です」

**回答:**そのとおりですが、なぜそれを選ぶのかを理解しましょう。

**対応:**オブジェクトにはinterfaceを、それ以外にはtypeを基本として使います。

2. 「インターフェースのユニオンが必要です」

圧力:「自分のインターフェースをユニオンにしたいです」

**回答:**型エイリアスを使えば、インターフェースをユニオンにできます。

対応:type Either = InterfaceA | InterfaceB;

要注意事項 - 立ち止まって再検討する

  • タプル型にinterfaceを使う
  • 宣言マージが必要なのにtypeを使う
  • コードベース内の類似した型で使い方に一貫性がない

よくある正当化(すべて誤り)

言い訳実際のところ
「まったく同じです」いいえ。宣言マージとユニオン型に違いがあります
「常にtypeのほうが優れています」interfaceのほうがエラーがわかりやすく、宣言マージもサポートします
「常にinterfaceのほうが優れています」ユニオン型、タプル、マップ型は表現できません

クイックリファレンス

// USE INTERFACE FOR:
interface User { name: string; }       // Object types
interface Config extends Base { }       // Extendable types

// USE TYPE FOR:
type ID = string | number;              // Unions
type Point = [number, number];          // Tuples
type Handler = () => void;              // Functions
type Keys = keyof User;                 // Computed types
type Mapped = { [K in Keys]: boolean }; // Mapped types

要点

オブジェクト型にはinterfaceを、それ以外にはtypeを使います。

インターフェースは宣言マージをサポートし、よりわかりやすいエラーメッセージを出力します。ユニオン、タプル、複雑な型操作には型エイリアスが必要です。コードベース内で一貫性を保ちましょう。

参考資料

Dan Vanderkam著『Effective TypeScript』のItem 13「Know the Differences Between type and interface」に基づいています。

コメント