TypeScript の Interface と Type Alias の違い

TypeScript の Interface と Type Alias は、TypeScript のバージョンが上がるにつれ、どんどん同じような機能を有するようになってきています。

それならどっちかしか必要ないんじゃないか?
Interface で書くか Type Alias で書くかどちらでもよいのなら、迷ってしまいよくないのでは?

そんなことを考えてしまったので、ちゃんと調べてみました。

ちなみに、type は Type Alias または型エイリアスと呼ばれていることが多かったので、本記事でも Type Alias で通しています。

三行まとめ

  • Interface はオープン
  • Type Alias はクローズド
  • どちらを使えばいいかはケースバイケース

Interface

Interface は、オブジェクトや関数、クラスの 仕様を定めるため のもの

用途

  • クラスやオブジェクトの規格を定義する
  • オブジェクト、クラス、関数の抽象型を定義するのに適している

性質

Type Alias

Type Alias は、複数の場所で再利用しようと思っている型に対して 名前をつけるためのもの

用途

  • 型や型の組み合わせに別名を付ける
  • 再利用したい複雑な型(複合的な型など)に名前を付けて再利用可能にする
  • 複雑な型の宣言に適している
    • 理由: 交差型, ユニオンタイプ、MappedType が使えるため
    • ユーティリティタイプなども Type Alias を使って書く

性質

  • 継承
    • 交差型(&)を使えば同じようなことは可能
      • ただし上書きはできない(後述)
    • 交差型で Interface を & することも可能
    • & した際に同名のプロパティ(要素)を宣言した場合重複する(後述)
  • オブジェクトと関数以外の型宣言でも使用可能
    • プリミティブ、配列、タプルなど
  • 宣言時に = が必要
  • class への implements が可能
  • MappedTypes が使える
  • index シグネチャが使える
  • 宣言されているプロパティ以外のプロパティが存在することが 考慮されない
  • 拡張に対してクローズド
    • 同じ type を宣言するとエラー

結局どっち使えばええのん?

ケースバイケースです。
強いて言うなら、コードベース(プロジェクト)全体を通した仕様としての型には Interface を、そうではない型には Type Alias を使うのがいいのかなと思いました。

参考コード

index シグネチャ

interface Options { [key: string]: any; }

function getUser (options?: Options = {}) {
  // ...
}
const options = { params: { id: 21 } }
let user = getUser(options); // OK

MappedType

普通にやってたらあまり使わないけど、便利タイプを作る時とかわりと使うかも。
keyof, typeof などと合わせて使うことが多いかも。

type Fruits = 'Apple' | 'Orange' | 'WaterMelon';

// {
//   'Apple': number;
//   'Orange': number;
//   'WaterMelon': number;
// }
type PriceOfFruits = { [key in Fruits]: number; }

// NG
interface PriceOfFruits { [key in Fruits]: number; }

交差型(Intersection Type)

通称かつオペレータ。上書きしようとした場合、両方を満たす必要が出てくる。制約を追加するイメージ(まさに &)。

type Point = { x: number, y: number }
type LabelledPoint = Point & { label: string } // GOOD

type Point2 = Point & { y: number | string } // NOT GOOD
const p2: Point2 = { x: 20, y: '20px' } // Error! y は number しか許容しない

拡張に対してオープン

interface User {
  id: number;
  name: string;
}

interface User {
  age: number;
}

// User {
//   id: number;
//   name: string;
//   age: number;
// }

宣言されているプロパティ以外のプロパティが存在することの考慮について

日本語がわかりづらいので具体的なコードで示す。

declare function getX(obj: { [key: string]: number }): number;

type Point = { x: number, y: number }
interface IPoint { x: number, y: number }

const pointA = { x: 1, y: 9, z: 0 }
getX(pointA) // OK

const pointB: Point = { x: 40, y: 120 }
getX(pointB) // OK

const pointC: IPoint = { x: 23, y: 46 }
getX(pointC) // Error!

参考にした記事

sotszk
福岡で Web のエンジニアをしています。
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
この記事は以下の記事からリンクされています
kouchanneAnyダメ絶対! axios編からリンク
Rui1009Typescript型Tips集からリンク
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした