静的型付け言語であるTypeScriptには、型を定義する構文として interface と type という二つのキーワードがある。
これらはコンパイル時の型チェックのために存在し、JavaScriptへのコンパイル後には消去される。
オブジェクトの形で型を定義するという用途において両者は、同等のものを生成できる。そのためどちらを使うべきか迷う場面もあるだろう。
// interface で定義する場合
interface User {
name: string
age: number
}
// type で定義する場合
type User = {
name: string
age: number
}
それぞれの構文を見ると一見同じように見えるが、両者にはいくつかの明確な違いがある。 本記事ではその違いを軽く整理してみようと思う。
1. 継承の方法が異なる
interface は extends キーワードで他のinterfaceを継承できるのに対し、type は &(交差型) を使って型を合成する。
結果として得られる型は似ているが、構文としてのアプローチが異なる。
// interface: extends による継承
interface Animal {
name: string
}
interface Dog extends Animal {
breed: string
}
// type: & による合成
type Animal = {
name: string
}
type Dog = Animal & {
breed: string
}
interface の extends は、親から子へと血筋を引き継ぐように型を受け継ぐ感じだ(「犬は動物の一種である」という is-a の関係)。
一方 type の & は、既存の型同士を粘土のように混ぜ合わせて新しい型を作る。
どちらも最終的な形は同じだが、「継承」なのか「合成」なのかという思想の違いがある。
2. 宣言のマージに関して
interface は同名で複数回宣言すると自動的にマージされる。
これはライブラリの型定義を拡張したい場合などに便利だが、意図せず型が変わってしまうリスクもある。
一方 type は同名で再宣言するとコンパイルエラーになるため、こうした問題は起きない。
// interface: 同名宣言がマージされる
interface Config {
debug: boolean
}
interface Config {
verbose: boolean
}
// 結果: Config は { debug: boolean; verbose: boolean } になる
// type: 同名で再宣言するとエラー
type Config = {
debug: boolean
}
type Config = { // ❌ Error: Duplicate identifier 'Config'
verbose: boolean
}
interface のマージは、共有のホワイトボードに誰でも付箋を追加できる状態に似ている。
チームで自由に情報を書き足せるのは便利だが、知らない間に誰かが書き足していて「あれ、こんなんあったっけ?」となるリスクがある。
type は、一度書いたらロックされるので変更したければ明示的に新しいバージョンを作る必要がある。
安全性と柔軟性のトレードオフだ。
3. 表現できる型の範囲にも差がある
interface はオブジェクトの形状や classが実装する契約(interface で事前設計したものを class に implementsキーワード を用いて従わせる。) を定義するのに適しているが、ユニオン型やタプル型などを直接表現することはできない。
type はこれらを含むあらゆる型に別名を付けられるため、より柔軟な型表現が可能だ。
// type ならではの表現
type Status = "loading" | "success" | "error" // ユニオン型
type Coordinate = [number, number] // タプル型
type Callback = (value: string) => void // 関数型
// interface ではこれらを直接表現できない
// interface Status = "loading" | "success" | "error" ❌
interface は「設計図」に近い。
オブジェクトが持つべきプロパティやメソッドの形を定義する。
一方 type は「ラベル」に近い。
どんなものにも名前を付けられる。瓶の中身が液体でも粉でも混合物でも、ラベルを貼れる。
だからユニオン型のような「AかBかCのどれか」という抽象的な概念にも名前を付けることができる。
最後に使い分け指針に触れておく。
まずそもそもユニオン型やタプル型など、オブジェクト以外の型を表現するならtypeを使う。
ここに選択の余地はない。
一方、オブジェクトの形状を定義する場面では interface と type のどちらでも書けるが、宣言マージによる拡張性やエラーメッセージの読みやすさから interfaceを使うようにする。(これらの点で公式ドキュメントでは interface の使用が推奨されている)
こんな感じ🍜