【TypeScript】型定義でのinterfaceとtypeの違い

ゆーたろー

こんにちは、ゆーたろーです。

HRTechベンチャーのエンジニアです。
TypeScript/Vue.js(Nuxt.js)/React/Next.js/PHP/LaravelでWebアプリケーションの開発を行っています。

・副業でSassスタートアップで開発(TS/React/Next.js)
・神戸で勉強会「つながる勉強会」を運営
・神戸メインのグルメインスタ運営

など色々やっています。1児のパパです。

TypeScriptで型定義をする方法は直書きを除くと大きく分けてinterfacetypeです。

本業では主に(というかほぼ全て)typeを使っているのですが、副業のソースコードでinterfaceが結構使われていて「両方型定義に使えるけど、どういう違いがあるんだろう?」と思ったので調べた内容をまとめておきます。

前回はTypeScriptの根幹の技術であるについてまとめました。

目次

interface

JavaScript/TypeScriptでクラスを使うときに使われる「実装する具象クラスの中身を制限するもの」です。(すごいふわっとした言葉で表しています)

implementsを使ってインターフェース(抽象クラス)の具象クラスを実装します。
(これはPHPでもお馴染みですね)

interface SampleInterface {
  name: string;
}

// 文字列のnameプロパティを持つことが強制される
class SampleClass implements SampleInterface {
  name = 'ゆーたろー';
}

// nameプロパティを持たないクラスはエラー
class SampleClass implements SampleInterface {
}
// Class 'SampleClass' incorrectly implements interface 'SampleInterface'. Property 'name' is missing in type 'SampleClass' but required in type 'SampleInterface'.

JavaScriptのクラス構文についてはこちらで説明されています。

JavaScriptのクラスではコンストラクタ、メソッド、プロパティのことをまとめてメンバーと呼ぶそうです。

なのでinterfaceは実装する具象クラスのメンバー(コンストラクタ、メソッド、プロパティ等)を制限するもの」ということになります。

そのinterfaceですが、オブジェクトの型定義にも使えます。

interface SampleInterface {
  name: string;
  age: number;
  isAdult: boolean;
}

const user1: SampleInterface = {
  name: 'ゆーたろー',
  age: 29,
  isAdult: true
};

// isAdultを持たないとエラー
const user2: SampleInterface = {
  name: 'ゆーたろー',
  age: 29,
};
// Property 'isAdult' is missing in type '{ name: string; age: number; }' but required in type 'SampleInterface'.

型定義のシーンでもクラスでの使い方と同様に型定義したオブジェクトのプロパティを強制する役割があります。

type

typeはTypeScript独特の構文で正式には型エイリアスと呼ばれます。(interfaceはJavaScriptでも使える)

文字通り型定義を扱うものであり、基本的な使い方はinterfaceと同じで、オブジェクトや配列の型を強制するものです。

type SampleType = {
  name: string;
  age: number;
  isAdult: boolean;
};

const user1: SampleType = {
  name: 'ゆーたろー',
  age: 29,
  isAdult: true
};

// isAdultを持たないとエラー
const user2: SampleType = {
  name: 'ゆーたろー',
  age: 29,
};
// Property 'isAdult' is missing in type '{ name: string; age: number; }' but required in type 'SampleType'.

interfaceとtypeの違い

書き方

まず、技術的な話ではないですが、書き方が違います。

// interface
interface SampleInterface {
  name: string;
  age: number;
  isAdult: boolean;
}

// type
type SampleType = {
  name: string;
  age: number;
  isAdult: boolean;
};

細かいですが、

  • interfaceは宣言のみ、typeは代入(typeには=が必要)
  • type式の末尾にはセミコロンが必要

という違いがあります。

ただ、interface、typeのメンバーのセミコロンやtypeの末尾のセミコロンもなくても問題なく動作します。

公式ドキュメントの説明

TypeScriptの日本語ドキュメントにはこのように載っており、複数の型アノテーションをまとめるときにはinterfaceが主要な方法であると書いています。

インターフェースは、複数の型アノテーションを単一の名前付きアノテーションに合成するための、TypeScriptにおける主要な方法です。

https://typescript-jp.gitbook.io/deep-dive/type-system

ずっとtypeが推奨されていると(勝手に)思っていたので意外でしたね。。

type(型エイリアス)についてはこのように書かれています。

TypeScriptは、複数の場所で使用したい型アノテーションの名前を提供するための便利な構文を提供します。エイリアスは type SomeName = someValidTypeAnnotation構文を使用して作成できます。

https://typescript-jp.gitbook.io/deep-dive/type-system

まあ色々調べた結論ですが、結論typeでも全く問題ないのではないかと思いましたけどね。

使える型の種類

それぞれで使える方の種類を比べるとオブジェクトの型定義では違いはありません。intersection型、union型も問題なく使えます。

type SampleType = {
  name: string;
  age: number;
  isAdult: boolean;
  address: {
	  address1: string,
	  address2: string,
	  address3: string,
  };
  hobbies: Array<string>;
  qualifications: Array<string> | null
  intersectionObj: {prop1: string} & {prop2: number}
}

interface SampleInterface {
  name: string;
  age: number;
  isAdult: boolean;
  address: {
	  address1: string,
	  address2: string,
	  address3: string,
  };
  hobbies: Array<string>;
   qualifications: Array<string> | null
  intersectionObj: {prop1: string} & {prop2: number} 
};

違いとしては、interfaceはオブジェクト、クラスの型定義にしか使えないのに対し、typeはより柔軟な型定義ができる点です。

例えば、特定の文字列や数字のunion型とか。

type Fruits = 'apple' | 'banana' | 'orange';
type Numbers = 1 | 3 | 5;

このようなオリジナルな型を使うケースではinterfaceを使うことができないので、typeを使う必要があります。

拡張性

以下のコードを例にします。

// interfaceの拡張
interface UserInterfase {
	name: string;
}

interface UserInterfase {
	age: number;
	isAdult: boolean;
}

const user: UserInterfase = {
	name: '太郎',
	age: 18,
	isAdult: false
}

同じ名前のinterfaceを再度定義することでプロパティ名と型のセットが既存のinterfaceに追加されます。(上書きではありません)

typeではこれはできません。

// typeの拡張はできない
type UserType = {
  name: string;
}

type UserType = {
  age: number;
  isAdult: boolean;
}
// 同じ名前の型は定義できない
// Duplicate identifier 'UserType'.

同じ名前の型エイリアスを定義することはできないです。定数と同じ扱いですね。

ただ、typeを使っても型の拡張自体はできます。

// typeの拡張(&を使う)
type UserType1 = {
  name: string;
}

type UserType2 = {
  age: number;
  isAdult: boolean;
}

// intersection型を定義して拡張
type User = UserType1 & UserType2

const user: User = {
  name: '太郎',
  age: 18,
  isAdult: false
}

&を使ってintersection型の型を新たに作ることで既存の型を拡張することができます。

じゃあどっちを使うのが良いのか?

個人的にはプロジェクトで好きな方を使えば良いのではないかと思っています。

もちろんどちらかを使うなら片方しか使わないというルールは必要かと思います。

個人的には関数コンポーネントでの記法がメジャーになってからReact/Next.jsを触り始めたので、
わざわざ(元々)クラス用のinterfaceを使う必要もないかなと思っているのと、文字通り型定義であることが分かりやすいという理由より、指定がなければtypeを使うようにしています。

(これが正解かは分かりませんが…)

あと、参考記事の中にも書いていましが、interfaceは同じ名前で宣言することで拡張されるので万が一思わぬところで既存のinterfaceと同じ名前のinterfaceを定義すると拡張されてしまいバグに繋がる可能性もあるというのも1つ重要なところかなと思います。

参考程度ですが、Twitterでアンケートをとってみました。

58票と多くはないですが、半数以上の方がtypeに統一して使っているというデータなので、typeを使うということで問題ないかなという個人的な結論です。

最後に

interfaceとtypeの違いを調べてまとめて2つの特徴を理解できました。

現役エンジニアの多くが型定義にどちらを多用しているのかは分かりませんが、個人的にはtypeを使っていこうと思います。
(色々な方の意見を聞きたい…)

参考記事

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

この記事を書いた人

Webエンジニア←KHI(プラント事業の技術職)←大学院(機械工学)

PHP/Laravel/TypeScript/React/Next.js(Vue.js/Nuxt.jsは少し)
バックエンド歴の方が長いです。

神戸で「つながる勉強会」を運営↓
https://tsunagaru-kobe.connpass.com/

コメント

コメントする

目次
閉じる