TypeScriptを書いていると「型定義ってtype
、 interface
どっち使えばいいの?」ってなることありませんか?今回は初学者でもなるべく簡単に選べるシンプルルールをまとめました。2025年8月現在最新版(TS 5.9)に対応しています。
結論から(シンプルルール)
-
オブジェクトの“形”を決めたい →
interface
(クラスやオブジェクトの設計図。拡張もしやすい) -
型を“組み合わせたり計算”したい →
type
(Union・タプル・条件付き・Mappedなどの型操作向け) -
両方で書けるなら → チームや現場ルールに合わせる
type
についての詳しい拡張方法についてはこちらにまとめたので気になる方はぜひ。
https://qiita.com/kinopy513/items/73b72304a41f21851cef
そもそも何が違うの?
-
interface
は「オブジェクトの形」を表すのが本職。宣言マージ(同名での拡張)ができるので、公開APIやライブラリ拡張と相性抜群。 -
type
は「型に別名をつける」だけにとどまらず、Union/タプル/条件付き/Mappedなど、型を“いじる”のが得意。
どちらもオブジェクトは書けるけれど、得意分野が違います。
基本の書き方
interface:オブジェクトの設計図
interface User {
id: number;
name: string;
}
const userData: User = {
id: 1,
name: "Lily"
}
type:別名+型操作いろいろ
type User = {
id: number
name: string
}
// Union
type Status = "active" | "inactive" | "deleted"
// タプル
type Point = [number, number]
// 条件付き・Mappedの土台にも
type WithTimestamp<T> = T & {
createdAt: Date
updatedAt: Date
}
できる/できない 早見表(TS 5.9時点)
目的 | interface | type |
---|---|---|
オブジェクトの形を定義 | 〇 | 〇 |
クラスに implements させる |
〇 | 〇 (※Union等は不可) |
Union(A | B ) |
× | 〇 |
タプル([A, B] ) |
× | 〇 |
条件付き・Mappedなどの型操作 | △ | 〇 |
同名での宣言マージ(後から足す) | 〇 | × |
ポイント
-
クラスはオブジェクト型なら
type
でもimplements
可能。ただしUnion型は不可。 -
ライブラリ型を拡張したいなら
interface
一択(宣言マージが効く)。
場面別使用例
1) API/DTO など公開する場合は interface
export interface UserDTO {
id: number;
name: string;
email?: string;
}
後からプロパティを足す余地がある場合は interface
が安全。
2) 状態の分岐は type のUnion
interface Loading {
type: "loading"
}
interface Success {
type: "success"
data: string
}
interface Failure {
type: "failure"
error: Error
}
type FetchState = Loading | Success | Failure;
列挙的な分岐はtypeのUnionで表現。
3) Props/設定の“合成”は type が書きやすい
type WithTimestamp<T> = T & {
createdAt: Date
updatedAt: Date
}
interface Base {
id: string
}
type Entity = WithTimestamp<Base>;
4) 既存ライブラリの型を後から拡張(module augmentation)
// d.ts で宣言マージ(interface だけが可能)
declare module "express-serve-static-core" {
interface Request {
userId?: string;
}
}
5) 設定オブジェクトの“型だけ満たす”チェック(satisfies
)
type RGB = readonly [number, number, number]
type Palette = Record<"red" | "green" | "blue", string | RGB>
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255], // スペルミス"bleu"を検出
} satisfies Palette
satisfies
は値の型を変えず「その型を満たしているか」を検証できるので安全な設計が可能です。
よくあるつまずき
1) 交差型(&
)の衝突に気づきにくい
// id は両立できず実質使えない型に
type A = { id: string } & { id: number }
“型を合成”したいだけなら interface
の extends
のほうが衝突に気づきやすいです。
2) type
は“同名で足していく”ができない
type User = { id: number }
type User = { name: string } // 再宣言エラー
後から増える見込みがあるなら最初から interface
の方がシンプルに実装できる。
3) クラスは Union(A | B
)を implements
できない
implements
は「持つべき型を確定」させる必要があるため、“どちらか”の Union は不可。
クラスの型は interface
(またはオブジェクト型の type
) か その交差(A & B
) で指定します。
type S = { x: number } | { y: number } // ❌ class C implements S
interface X { x: number }
interface Y { y: number }
class P implements X, Y { x = 0; y = 0 } // ⭕️ A & B
const v: X | Y = new P(); // 使う側の Union はOK
チームで迷わないための最小ルール
-
原則
-
オブジェクトの型 →
interface
-
Union/タプル/条件付き/Mapped →
type
-
-
例外
- 内部の一時的な合成・変形が中心なら
type
を使ってもよい
- 内部の一時的な合成・変形が中心なら
-
Lintで固定
{
"rules": {
"@typescript-eslint/consistent-type-definitions": [
"error",
"interface"
]
}
}
「オブジェクトは interface」を機械的に守れます。
※ただし対象は素のオブジェクト型だけ。
Union/Intersection/条件型/Mapped型はinterface制限の対象外です
チーム方針として「公開されるオブジェクト形は interface、型演算は type」で割り切るなら、このLint固定はおすすめです。
補足
TypeScript 5.9では import defer
などが話題ですが、type
と interface
の基本的な性質や使い分けが変わったわけではないので、今まで通り使えます。
まとめ
-
公開する設計図(API/DTO/外部と接する型) →
interface
-
後から足す可能性がある型 →
interface
(宣言マージ) -
状態分岐・選択肢の列挙 →
type
(Union) -
固定長配列・位置に意味がある →
type
(タプル) -
型の合成・変形 →
type
(交差・条件付き・Mapped) -
クラスの型 →
interface
(またはオブジェクト型のtype
。Union不可)
採用情報
アシストエンジニアリングでは一緒に働くフロントエンド、バックエンドエンジニアを募集しています!
少しでも興味のある方は、カジュアル面談からでもお気軽にお話ししましょう!
お問い合わせはこちらから↓
https://official.assisteng.co.jp/contact/
参考