意味から見る「type」と「 interface」の使い分け
type と interface どっちを選べばいいんだ問題
話題になってるのを見かけたので、ちょっと自分でも書いておこうと思いました。
誰しもが一度は迷ったことがあるとは思いますが、言葉の意図を読み解いてあげることで、
- 基本は「interface」
- どうしても必要な場合だけ「type」
で、なぜ正しく動くのかが見えてきます。
まず大前提
Typescriptは、構造的型付けの言語です(対義語:名義的型付け)
これ、一体どういうことなのか?というと
Typescriptはついている名前ではなく、構造の一致で同じかどうかを見ています。
実例を見たほうが早いですね
interface Cat { name: string }
interface Dog { name: string }
const tama: Cat = { name: "Tama" }
const pochi: Dog = tama // OK
コレが通ってしまう言語です。
名義的型付け(CとかJavaとか)の言語と違って、別の名称の型(そもそも名称の概念ない)であっても、構造が一致していれば、同じものとしています。
つまり、Typescriptを使う上で「構造」というものが非常に大きな意味を持ちます。
一体何の話をしてるんだお前は?
はい、これが、type と interface を理解する上で必要になるからです。
先程も話したとおりですが「TypeScript」は「構造的型付け」の言語です。
そのため、構造を定義することが非常に重要な意味を持ちます。
type - aliasの定義
まず「type」ですが、これは「型にaliasをつける」書き方です。
つまり
type Name = string // string型に、Nameという別名をつける
type Age = number // number型に、Ageという別名をつける
type User = {
name: string
age: number
} // 右辺の構造に、Userという別名をつける
といった使い方をします。
別名をつける以外にも、
| 演算 | 用途 |
|---|---|
| & | 交差型(Intersection/共通部分、どっちも満たさないとダメ) |
| | | Union(和集合型、どっちか満たせばOK) |
| typeof | 変数などからの型の変換 |
| keyof | 構造内のKey抜き出し |
| infer | マッチングした型からの抜き出し |
といった、「型の演算ができること」が特徴です。
これ、演算という特性上、常に評価が行われるため、使い方を間違えると、延々と型の演算が走り続けてしまい、TSの静的評価が悲鳴をあげるという結果につながります。
たとえば
type TX = { x: number }
type TY = { y: number }
type TZ = TX & TY
これぐらいなら全然余裕ではありますが、こんな感じで延々と交差型が作られていき、その評価が常に行われ続けると想像してみてください。
やり方次第で、大変なことになりそうでしょ?
interface - 構造の定義
次は「interface」ですが、こっちは「構造そのものを定義する」という意味です。
つまり
interface User {
name: string
age: number
} // Userという構造を定義する
といった使い方をします。
さっきと変わらねぇじゃねぇか
はい、「interface」は構造を定義しているので、Typeで構造に別名をつけたのと大きくは変わりません。
- 「type」は無名の構造に、別名を付けた
- 「interface」は、構造自体を定義
といった違いですね。
つまり、type は「既に存在する構造や型」を再利用するためのラベルであり、
interface は「新しい構造そのもの」を定義するための設計図です。
とはいえ、interfaceは構造自体の定義なので、型の演算を使うことは出来ません。
ただし、結合のみは extends で出来てしまうので、これが理解をややこしくするのです。
実際に使うときの extends と implements
さて、定義と軽く特色のお話をしてきましたが、皆さん type で型を作ったり、interfaceで構造を定義したら、使いますよね?
そんときに、大体不思議に思うのは、
なんでクラス定義するときに使うのは、implements なのに、interface を結合するときは extendsなんだよ。Class定義とルール違うじゃねぇか
コレです。私もこう思ってました。
ただ、extends と implements の意味考えてないだけだと最近気が付きました。
extends と implements の意味の違い
extends → 拡張する
implements → 実装する
この違いです、英語の意味どおりに取れば良いんです。
classの場合
class xxx extends yyy implements zzz
この場合、xxx は、yyy の拡張(extends)ですよ。 また、このクラスは zzz に沿って実装(implements)します。
interfaceの場合
interface xxx extends yyy { ... }
この場合は、xxx のインタフェースは、yyy の拡張(extends)インタフェースですよ
Generics定義の場合
type xxx<T> = T extends yyy ? T : never
この場合は T は yyyを拡張(extends)した型ですよ。正しければ T、違ったら never を返してね
つまるところ、構造的型付け言語であるゆえ「TypeScriptは構造がどうなるか」にしか興味がないんです。
段々謎が解けてきた感じがしませんか?
結論というかなんというか
結局、何がいいたいのかって、
「type」と「interface」って意味的に違うものです。
ただ、どっちも構造を扱えるせいで、だいぶややこしいことになってます。
「type」は、いろいろな型を作り出せますが、常に評価が行われるため開発環境にとっての負荷が上がるケースがあります。
「interface」は、ただの構造定義であるため、拡張等した場合でも遅延評価となっており開発環境への負荷は低めです。
ただし、コレは結果的にそうなっているだけであり、使い分ける理由は
- 「type」は、型の別名や型の演算を行う場合に使いましょう
- 「interface」は、構造を定義するために使いましょう
この2つです。
「型を計算するのか、構造を定義するのか」
しっかり見極めてあげると、どっち使えばいいのかわかると思います。
まぁ、どっち使っても大体動くんですけどね。
でもせっかくなら、意図して選べるとカッコいいじゃないですかw
[おまけ] 🧩 type と interface の本質的な違い
最後に ChatGPTさんに、本質的な違いって何よ?ってきいたら、以下の重たい表が返ってきたので載せておきます。
| 要素 | type | interface |
|---|---|---|
| 目的 | 既存の型に別名(alias)をつける | オブジェクトの構造を定義する |
| 評価タイミング |
即時評価(Eager Evaluation) =その場で型を展開・確定 |
遅延評価(Lazy Evaluation) =参照されるまで構造を評価しない |
| 構文の特徴 |
= を使い、型を代入できる |
extends による継承で構造を拡張する |
| 演算サポート |
&, |, typeof, keyof, infer など、型演算が可能
|
構造の合成(extends) のみ |
| 主な用途 | - Union / Intersection 型の結合 - 条件型・ジェネリクスによる型変換 - プリミティブ型の別名定義 |
- クラスやオブジェクトの構造定義 - 実装( implements)の契約定義 |
| 拡張方法 |
type A = B & C(交差型による拡張) |
interface A extends B(継承による拡張) |
| 実装(implements) | 構造が一致していれば可能(非推奨ではない) | 通常はこちらで行う(推奨) |
| 評価コスト | 複雑な型操作を多用するとパフォーマンス低下 | 構造名として扱われるためスケーラブル |
| 得意分野 | 型操作・ユーティリティ型 | 構造定義・契約設計 |
| 使い分けの指針 | 「型を計算したいときに使う」 | 「形を保証したいときに使う」 |
💡 まとめ
typeは “計算と合成のための道具”。interfaceは “形を保証するための契約”。
両方とも「構造的型付け」の上に立っているが、目的と評価のタイミングがまったく違う。
💬 この記事は TypeScript で遊び倒してたら見えてきた話なので、間違い等あればご指摘ください。
Discussion