TypeScript インターフェイスと型: 実際の違い、コンパイラでテスト

  1. ホーム
  2. ブログ
  3. ソフトウェア開発

ソフトウェア開発

TypeScript インターフェイスと型: コンパイラでテストした実際の違い

インターフェイスと型エイリアスは重複する部分が多いため、選択が恣意的であるように感じられることがよくあります。ここでは、実際のコンパイラ エラーによって確認された、それぞれができて他方ができないことと、チームが従うことができる簡単なルールを示します。

読了目安8分
TypeScript インターフェイスと型: 実際の違い、コンパイラでテスト

ポイント

  • プレーン オブジェクト シェイプの場合、インターフェイスとタイプ エイリアスはほぼ互換性があります。
  • type のみが共用体、タプル、およびその他の非オブジェクト型に名前を付けることができます。
  • interface のみが、2 つの宣言を結合する 宣言のマージ をサポートしています。
  • 共通ルール: オブジェクト シェイプにはインターフェイス type、その他すべてには一貫して適用されます。

TypeScript に関する質問は、TypeScript インターフェイスと型 ほど多くの議論を引き起こすものはほとんどありません。多くの記事には、以前のバージョンでは重要であったが現在は古くなっている相違点がリストされています。そこで、主張を繰り返すのではなく、それらをテストしました。

それぞれの違いを検証する 1 つのファイルを作成し、TypeScript 5.6.3 を strict モードで使用してチェックしました。以下に引用するコンパイラ エラーは、その実行によって発生します。動作はバージョン間で異なる可能性があるため、プロジェクトで使用している TypeScript のバージョンを確認してください。この言語を初めて使用する場合は、初心者向けの TypeScript チュートリアルから始めてください。

短い答え

interface はオブジェクトの形状を表し、拡張したり再度開いたりすることができます。 type エイリアスは、オブジェクト、共用体、タプル、関数型など、任意の 型に名前を付けます。どちらも機能する場合、違いは主にスタイルです。

どこが同じなのか

これら 2 つの宣言は同一の形状を記述します。

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

type UserAlias = {
  id: number;
  name: string;
  email?: string;
};

const a: User = { id: 1, name: "Asha" };
const b: UserAlias = a;   // no error

TypeScript が使用するもの構造的型付けつまり、名前ではなく形状を比較します。だからこそ、Userに割り当てることができますUserAlias文句なしで。どちらもオプションのプロパティをサポートしています。readonlyプロパティ、メソッド、インデックス署名。

インターフェイスとタイプを並べて表示

特徴 インターフェース タイプ
オブジェクトの形状 はい はい
共用体 ("a" | "b" など) いいえ はい
タプルとプリミティブ いいえ はい
マップされた型と条件付き型 いいえ はい
宣言のマージ はい いいえ、エラーです
延長中 伸びる &交差点
矛盾するプロパティ 宣言時のエラー 静かになれるnever
implementsと一緒に使用します はい はい、オブジェクトタイプの場合

type だけでできること

多くの有用な型はまったくオブジェクト形状ではないため、型エイリアスのみがそれらに名前を付けることができます。

type Status = "pending" | "paid" | "shipped";   // union
type Point = [number, number];                    // tuple
type Handler = (event: string) => void;           // function type
type Keys = keyof User;                           // computed type
type ReadonlyUser = Readonly<User>;               // utility type

コンパイラはリスト外の値を拒否するため、ユニオン型は TypeScript の最も価値のある機能の 1 つです。

let s: Status = "paid";
s = "refunded";

// error TS2322: Type '"refunded"' is not assignable to type 'Status'.

これにより、本番環境に到達する前に、コンパイル時にタイプミスや古い値が検出されます。

宣言のマージ

同じ名前のインターフェイスを 2 回宣言すると、TypeScript はそれらを 1 つにマージします。

interface Settings {
  theme: string;
}
interface Settings {
  language: string;
}

const settings: Settings = { theme: "dark", language: "en" };   // no error

型エイリアスで同じことを試みると、コンパイラは拒否します。

type Config = { theme: string };
type Config = { language: string };

// error TS2300: Duplicate identifier 'Config'.

マージは、ライブラリがその型を拡張できるようにする方法です。たとえば、ライブラリを変更せずに、グローバル Window インターフェイスやフレームワークのリクエスト オブジェクトにカスタム プロパティを追加できます。それは本当に役に立ちます。

逆に、マージは偶然に起こる可能性もあります。プロジェクト内の 2 つのファイルが同じスコープ内で同じ名前のインターフェイスを宣言すると、それらは暗黙的に結合されるため、混乱を招く可能性があります。型エイリアスを使用すると、代わりにその衝突がエラーになります。

延長と交差

どちらも既存の型に基づいて構築できます。

interface Admin extends User {
  permissions: string[];
}

type AdminAlias = UserAlias & { permissions: string[] };

互換性のあるプロパティの場合、結果は同じになります。違いは競合として現れます。インターフェイスがプロパティの型を互換性のないように変更しようとすると、コンパイラはそれをすぐに報告します。

interface Base { id: number }
interface Broken extends Base { id: string }

// error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
//   Types of property 'id' are incompatible.
//     Type 'string' is not assignable to type 'number'.

交差点は宣言されても文句を言いません。代わりに、競合するプロパティは never、つまりどの値も持つことができない型になります。このエラーは、後で使用しようとしたときにのみ表示されます。

type Mixed = { id: number } & { id: string };
const m: Mixed = { id: 1 };

// error TS2322: Type 'number' is not assignable to type 'never'.

この後のエラーは、実際の間違いとは異なる点を指摘しているため、さらに混乱を招きます。これが、オブジェクト タイプを構築するときに extends を優先する実際的な理由の 1 つです。

クラスと実装

クラスは implements を使用して形状と一致することを約束できます。これはインターフェイスまたはオブジェクト タイプのエイリアスで機能します。

class Account implements User {
  constructor(public id: number, public name: string) {}
}

これはエラーなしでコンパイルされました。 implements はクラスが一致することのみをチェックすることに注意してください。プロパティやコードは追加されません。一致する単一の形状が存在しないため、クラスは共用体型を実装できません。

読み取り専用タイプとユーティリティタイプ

Readonly、Partial、Pick、Omit などのユーティリティ タイプは、インターフェイスとエイリアスの両方で動作します。結果は新しい型であり、通常は型のエイリアスで名前が付けられます。

const r: ReadonlyUser = { id: 3, name: "Dev" };
r.name = "Chitra";

// error TS2540: Cannot assign to 'name' because it is a read-only property.

readonly はコンパイル時のみのチェックであることに注意してください。実行時にはオブジェクトは通常の JavaScript ですが、型をバイパスするコードによって変更することができます。

ヒント: TypeScript が JavaScript にコンパイルされると、型は失われます。 API 応答など、外部から到着するデータは、型があるという理由だけで実行時にチェックされません。たとえばスキーマ ライブラリを使用して検証します。 Fetch API チュートリアルでは、API 応答の安全な処理について説明しています。

コンパイラのパフォーマンス

TypeScript チーム独自のパフォーマンス ガイダンスでは、コンパイラがインターフェイスの関係をより効率的にキャッシュできるため、大きな交差では extends を使用するインターフェイスを優先することが提案されています。小規模および中規模のプロジェクトでは、違いに気づくことはほとんどありません。これは、型が深く構成された非常に大規模なコードベースでは重要になる可能性があります。私たちはこれを自分たちで測定したわけではありません。

どれを使用するべきですか?

TypeScript ハンドブックのガイダンスに従って、広く使用されているルールは次のとおりです。

  • オブジェクト シェイプ、特にパブリック API、およびクラスが実装するものや他のクラスが拡張するものには interface を使用します。
  • 共用体、タプル、関数型、およびマップ型または計算型には type を使用します。
あなたが定義しているのは 使用する
ユーザーや注文などのデータ モデル インターフェース
許可される値のセット typeユニオン
固定長のペアまたはトリプル typeタプル
コールバック署名 タイプ
ライブラリの型を拡張する interface、マージを通じて
既存タイプのバリエーション type ユーティリティタイプの場合

一部のチームはどこでも type を単純に使用しますが、これもうまく機能します。最も重要なのは一貫性であるため、規則を選択し、それをスタイル ガイドに記述し、リンターに強制させます。 typescript-eslint の ESLint の consistent-type-definitions ルールはこれを行うことができます。

よくある間違い

  • 終わりのない議論。 オブジェクトにはどちらも問題ないため、一度ルールを決めて次に進みます。
  • 偶発的なマージ。 同じスコープ内でインターフェイス名を再利用すると、それらがサイレントに結合されます。
  • 競合する交差。 後で混乱を招くエラーを含む never タイプを作成します。
  • 実行時に型を信頼します。 これらは消去されるため、外部データを検証します。
  • any を使用してエラーを抑制します。 チェックをオフにします。 unknownを優先して絞り込みます。

既存のコードベースでのそれらの切り替え

TypeScript は型を名前ではなく構造で比較するため、単純なオブジェクト形状の場合は、interface を type に変更したり、その逆を変更したりすることは通常安全です。切り替える前に、いくつかの点を確認する必要があります。

  • インターフェイスを型エイリアスに変換すると、宣言のマージが削除されるため、同じ名前を持つ 2 番目の宣言は重複識別子エラーになります。
  • 型エイリアスのインターフェイスへの変換は、オブジェクト シェイプに対してのみ機能します。共用体、タプル、およびマップされた型は、型のエイリアスのままにしておく必要があります。
  • エラー メッセージやエディターのホバーではタイプが異なって表示される場合があり、チームメイトを驚かせる可能性がありますが、動作は変わりません。

1 つの集中的なプル リクエストで変更を加え、コンパイラとテスト スイートを厳密モードで実行し、機能作業との混合を避けて、レビュー担当者が他に何も変更されていないことを確認できるようにします。

まとめ

インターフェイスかタイプかという質問には、冷静な答えがあります。それらはオブジェクトとして重複しており、それぞれにいくつかの固有の機能があり、選択よりも一貫性が重要です。チームが別の方法を希望しない限り、形状にはインターフェイスを使用し、その他すべてにはタイプを使用します。違いを自分で確認するには、上記の例をファイルにコピーし、そのファイルに対して tsc --noEmit --strict を実行してください。

よくある質問

TypeScript のインターフェイスと型の違いは何ですか?

どちらもオブジェクトの形状を記述することができます。型のみが共用体、タプル、計算型に名前を付けることができ、インターフェイスのみが宣言のマージをサポートします。

React props にはインターフェイスまたはタイプを使用する必要がありますか?

どちらでもうまくいきます。多くのチームはプロップにインターフェイスを使用しますが、他のチームはユニオンとの一貫性のために型を好みます。 1 つを選択して、プロジェクト全体に適用します。

型はインターフェイスを拡張できますか?

はい、type Admin = User & { Permissions: string[] } のような交差を使用することで可能です。インターフェイスは、オブジェクト タイプのエイリアスを拡張することもできます。

インターフェイスは入力よりも高速ですか?

TypeScript のパフォーマンス ガイダンスでは、大規模なコードベース内の大きな交差部分に拡張されるインターフェイスが優先されます。一般的なプロジェクトでは、違いが目立つことはほとんどありません。

宣言のマージとは何ですか?

これは、同じスコープ内の同じ名前を持つ 2 つのインターフェイスが 1 つに結合される場合です。型の別名はマージできず、名前が重複するとエラーが発生します。

学び続ける

Stack Quoraと連携する

テスト、WordPress、または SEO についてサポートが必要ですか?

私たちは、チームがより優れたソフトウェアを出荷し、サイトを成長させるのを支援します。あなたが取り組んでいることを教えてください。明確な次のステップを提案します。

読み続けてください

詳しいガイドはこちら テスト、AI、最新の開発

この記事は気に入りましたか?トピックを選択して、最新のチュートリアルに直接アクセスしてください。

人工知能

AI 幻覚の説明: 言語モデルが物事をでっち上げる理由とそれを軽減する方法

AI 幻覚について説明しました。幻覚とは何か、言語モデルが幻覚を生成する理由、一般的な種類、グラウンディングとプロンプトで幻覚を軽減する方法、そして私たちが実行した簡単なチェックです。

記事を読む →
DevOps

環境変数の説明: .env ファイル、シークレット、および設定が正しく行われている

実行した Python の例を使用して環境変数について説明します。環境変数の読み取りと設定、すべての値が文字列である理由、.env ファイル、プロセスの継承、シークレット、検証について説明します。

記事を読む →
ソフトウェア開発

API レート制限の説明: アルゴリズム、ヘッダー、および 429 エラーの処理

API レート制限については、実行したシミュレーションで説明します。トークン バケット、固定ウィンドウ、スライディング ウィンドウ、429 応答、レート制限ヘッダー、バックオフによる再試行、およびテスト制限です。

記事を読む →
テスト自動化

Python でのモッキング:unittest.mock、patch、およびテストを嘘にする間違い

Python でのunittest.mockによるモック、テスト済み: モックとreturn_value、side_effect、名前が使用されている場所のパッチ、仕様と自動仕様、日付のモック、およびよくある間違い。

記事を読む →
ソフトウェアテスト

コードカバレッジの説明: 回線カバレッジとブランチカバレッジ、および 100% の実際の意味

実際のcoverage.pyの実行でコードカバレッジを説明: 行、分岐、条件のカバレッジ、レポートの読み取り、100%のカバレッジでもバグを見逃す可能性がある理由、および賢明なターゲット。

記事を読む →
データベースとSQL

SQL における DELETE、TRUNCATE、DROP: 違い、ロールバック、およびそれぞれをいつ使用するか

DELETE、TRUNCATE、DROP について、テスト済み SQL を使用して説明します。それぞれの削除内容、ロールバック、トリガー、ID リセット、速度、PostgreSQL、MySQL、SQL Server、SQLite の違いについて説明します。

記事を読む →
データベースとSQL

SQL ビューの説明: 作成、使用、落とし穴の回避、例を示します

SQL ビューについて、実行したクエリとともに説明しました。ビューの作成、ライブ結果、列の非表示、パフォーマンス、更新可能なビューと具体化されたビュー、壊れた依存関係、ベスト プラクティスについて説明します。

記事を読む →
ソフトウェア開発

TypeScript インターフェイスと型: 実際の違い、コンパイラでテスト

TypeScript インターフェイスと型、実際のコンパイラでチェック: 両方でできること、片方だけでできること、宣言のマージ、拡張と交差、および従うべき簡単なルール。

記事を読む →
ソフトウェア開発

Python *args と **kwargs の説明: 柔軟な関数と明確な例

Python の *args と **kwargs について、実行したコードで説明しました。パッキングとアンパッキング、パラメーターの順序、キーワードのみの引数と位置のみの引数、デコレーター、一般的なエラーです。

記事を読む →
ソフトウェア開発

Python Lambda 関数: 構文、実際の用途、および使用すべきでない場合

Python のラムダ関数について、実行したコードとともに説明します。構文、キーによるソート、マップとフィルター、クロージャー、遅延バインディング トラップ、通常の def がより適切な選択である場合。

記事を読む →
人工知能

AIエージェントとは何ですか?シンプルなエージェント ループを実行できる仕組み

AIエージェントとは何ですか?エージェント ループ、ツール、メモリ、ガードレール、実行した小さな Python ループ、実際のリスク、およびシンプルなチャットボットがより良い選択である場合についてのわかりやすいガイドです。

記事を読む →
DevOps

モニタリングと可観測性: ログ、メトリクス、トレースによる本当の違い

モニタリングと可観測性について説明: 既知および未知の障害、ログ、メトリクスとトレース、チェックアウト インシデントのウォークスルー、ゴールデン シグナル、OpenTelemetry、および便利なアラート。

記事を読む →
DevOps

Docker ボリュームとバインド マウント: コンテナ データを正しい方法で保持する

Docker ボリュームとバインド マウントについて説明します。コンテナ データが消える理由、名前付きボリューム、バインド マウント、tmpfs、Compose の例、バックアップ、アクセス許可、およびそれぞれをいつ使用するかについて説明します。

記事を読む →
テスト自動化

TDD と BDD: 主な違い、1 つの機能が両方の方法で構築され、それぞれをいつ使用するか

TDD と BDD は、両方の方法で構築された 1 つの割引機能で説明されています。実行したコードによるレッド-グリーン-リファクタリング、Given-When-Then シナリオ、ツール、トレードオフ、およびそれぞれをいつ使用するかです。

記事を読む →
ソフトウェア開発

Git mergeとrebaseの違い:例と使い分け

Git mergeとrebaseの違いを、実際のログ出力、比較表、rebaseの黄金律、安全なチームワークフローで解説します。

記事を読む →
ソフトウェア開発

Pythonのリスト内包表記:構文・例・使うべきでない場面

Pythonのリスト内包表記を解説:構文、フィルタ、if-else、ネストしたループ、実測した処理時間、そして通常のforループの方が良い場面。

記事を読む →
データベースとSQL

SQL JOINを例で解説(Inner・Left・Right・Full)

サンプルのテーブルとクエリでSQL JOINを順に解説。INNER、LEFT、RIGHT、FULL、CROSS、SELFに加え、よくある間違いとパフォーマンスのコツも紹介します。

記事を読む →