TypeScriptの基礎 - オブジェクト型とインターフェース

提供: MochiuWiki : SUSE, EC, PCB

概要

TypeScriptでは、オブジェクトの構造を型として定義するための仕組みとして、オブジェクト型リテラルと interface の2つが提供されている。
これらは、オブジェクトが持つべきプロパティとその型を宣言することで、コンパイル時に構造の整合性を検証する役割を果たす。

2026年2月時点でTypeScript 5.9が安定版としてリリースされており、
TypeScript 6.0ベータでは strict: true のデフォルト化や target: "es2025" への変更等が含まれている。

さらに、Go言語で書き直されるTypeScript 7.0では最大10倍のコンパイル速度向上が見込まれている。

オブジェクト型の定義方法
定義方法 説明
オブジェクト型リテラル type キーワードと波括弧を用いてオブジェクトの形状を定義する方法
インラインでの型注釈としても使用でき、局所的な型定義に適している。
interface オブジェクトの構造を定義するための専用構文
オプショナルプロパティ、読み取り専用プロパティ、インデックスシグネチャ、
extends による拡張、Declaration Merging (宣言マージ) 等の機能を持つ。


オブジェクト型の定義には interface を優先し、ユニオン型やインターセクション型が必要な場合に type を使用するのが一般的な指針である。

パフォーマンスでは、interfaceextends はコンパイラが結果をキャッシュするため、
type& (交差型) より高速に処理される。

また、PartialPickOmitRecordRequired 等のユーティリティ型と組み合わせることにより、オブジェクト型の柔軟な変換が可能である。

TypeScript 4.9で導入された satisfies 演算子を使用すると、オブジェクトが特定の型を満たすことを検証しつつ、値の具体的な型情報を保持できる。

型エイリアスの詳細については、TypeScriptの基礎 - 型エイリアスのページを参照すること。


オブジェクト型リテラル

オブジェクト型リテラルとは、オブジェクトのプロパティ名と型の対応を波括弧で囲んで記述することにより、オブジェクトの形状を定義する記法である。
type キーワードと組み合わせて型エイリアスとして定義する、または、インラインで型注釈として使用することができる。

基本的な構文

type キーワードを使用してオブジェクト型を定義する基本的な構文は以下の通りである。

 type <型名> = {
    <プロパティ名>: <>;
    <プロパティ名>: <>;
 };


具体的な例を以下に示す。

 type Person = {
    name: string;
    age: number;
 };
 
 function greet(person: Person) {
    return "Hello " + person.name;
 }
 
 const alice: Person = { name: "Alice", age: 30 };
 console.log(greet(alice));  // "Hello Alice"


複数のプロパティを持つ型も同様に定義できる。

 type User = {
    id: number;
    username: string;
    email: string;
    isActive: boolean;
 };


オブジェクト型はインラインで型注釈として使用することもできる。
型エイリアスを定義するほどではない局所的な用途に適している。

 function printUser(user: { name: string; age: number }) {
    console.log(`${user.name} (${user.age})`);
 }


ネストしたオブジェクト型

オブジェクト型のプロパティに別のオブジェクト型を指定することにより、ネストした構造を表現できる。

 type Address = {
    street: string;
    city: string;
    country: string;
    postalCode: string;
 };
 
 type UserWithAddress = {
    id: number;
    name: string;
    address: Address;
 };
 
 const user: UserWithAddress = {
    id: 1,
    name: "Alice",
    address: {
       street: "123 Main St",
       city: "Tokyo",
       country: "Japan",
       postalCode: "100-0001",
    },
 };


ネストしたオブジェクト型を個別の type として分離することにより、型の再利用性が高まる。


interface

interface はオブジェクトの構造を定義するための専用構文であり、TypeScriptが提供する重要な型定義の仕組みである。

オブジェクト型リテラルと同様にオブジェクトの形状を定義できるが、Declaration Merging (宣言マージ) や extends による拡張等、
interface 固有の機能を持つ。

interfaceの定義

interface キーワードを使用してオブジェクトの構造を定義する。

 interface User {
    id: number;
    name: string;
    email: string;
 }
 
 const user: User = {
    id: 1,
    name: "Alice",
    email: "alice@example.com",
 };


interfacetype によるオブジェクト型定義は、多くの場面で同等に使用できる。

オプショナルプロパティ

プロパティ名の後に ? を付与することで、省略可能なプロパティを定義できる。

オプショナルプロパティを持つオブジェクトは、そのプロパティを持たない形式でも型チェックをパスする。

 interface User {
    name: string;
    age?: number;  // オプショナルプロパティ
 }
 
 const user1: User = { name: "Alice" };         // OK : ageは省略可能
 const user2: User = { name: "Bob", age: 30 };  // OK : ageを指定してもよい


オプショナルプロパティの型は、指定した型と undefined のユニオン型として扱われる。
例えば、上記の age? の型は number | undefined となる。

オプショナルプロパティを参照する時には、undefined の可能性を考慮した処理が必要である。

 function displayUser(user: User): void {
    console.log(user.name);
 
    // ageがundefinedの場合を考慮する
    if (user.age !== undefined) {
       console.log(user.age.toString());
    }
 
    // オプショナルチェーン演算子を使用する方法
    console.log(user.age?.toString() ?? "年齢未設定");
 }


読み取り専用プロパティ (readonly)

readonly キーワードをプロパティ名の前に付与することにより、初期化後に変更できない読み取り専用プロパティを定義できる。

 interface SomeType {
    readonly prop: string;
 }
 
 function doSomething(obj: SomeType): void {
    console.log(obj.prop);  // 読み取りはOK
    obj.prop = "hello";     // エラー : 読み取り専用プロパティには割り当てできない
 }


readonly は配列型にも適用できる。

 function doStuff(values: readonly string[]): void {
    const copy = values.slice();  // 読み取りはOK
    values.push("hello!");        // エラー : 読み取り専用配列に追加できない
 }


Readonly ユーティリティ型を使用することにより、既存の型の全プロパティを一括して読み取り専用にすることもできる。

 type A = Readonly<{ a: string; b: number }>;
 // 結果: { readonly a: string; readonly b: number }


readonly はTypeScriptの型チェック上の制約であり、JavaScriptの実行時には影響しない琴に注意が必要である。

インデックスシグネチャ

事前にプロパティ名が分からない場合、インデックスシグネチャを使用して動的なプロパティの型を定義できる。

インデックスシグネチャは、[<キー名>: <キーの型>]: <値の型> という形式で記述する。

  • 文字列キーを使用するインデックスシグネチャの例
     interface BooleanDictionary {
        [key: string]: boolean;
     }
     
     let myDict: BooleanDictionary;
     myDict["foo"] = true;    // OK
     myDict["bar"] = false;   // OK
     myDict["baz"] = "oops";  // エラー: string は boolean に割り当てられない
    

  • 数値キーを使用するインデックスシグネチャの例
     interface StringArray {
        [index: number]: string;
     }
     
     const arr: StringArray = ["Alice", "Bob", "Carol"];
     const name: string = arr[0];  // OK
    


インデックスシグネチャを持つ interface に通常のプロパティを追加する場合、そのプロパティの型はインデックスシグネチャの値の型と互換性がなければならない。

 interface NumberDictionary {
    [index: string]: number;
    length: number;   // OK : numberはインデックスシグネチャの値型と一致する
    name: string;     // エラー : stringはnumberに割り当てられない
 }


また、keyof 演算子を文字列インデックスシグネチャに適用すると、string | number になる琴に注意が必要である。
これは、JavaScriptにおいて、数値キーは内部的に文字列として扱われるためである。

 type Mapish = { [k: string]: boolean };
 type M = keyof Mapish;  // string | number



interfaceの拡張 (extends)

extends キーワードを使用することにより、既存の interface のプロパティを継承した新しい interface を定義できる。

 interface BasicAddress {
    name?: string;
    street: string;
    city: string;
    country: string;
    postalCode: string;
 }
 
 interface AddressWithUnit extends BasicAddress {
    unit: string;  // BasicAddress の全プロパティに加えて unit を追加
 }
 
 const address: AddressWithUnit = {
    street: "123 Main St",
    city: "Tokyo",
    country: "Japan",
    postalCode: "100-0001",
    unit: "3F",
 };


複数の interface を同時に拡張することもできる。
カンマ区切りで複数の interface を指定することにより、それらの全プロパティを継承できる。

 interface Colorful {
    color: string;
 }
 
 interface Circle {
    radius: number;
 }
 
 interface ColorfulCircle extends Colorful, Circle {}
 
 const cc: ColorfulCircle = {
    color: "red",
    radius: 42,
 };


extends による拡張は、共通のプロパティを持つ基底 interface を定義して再利用性を高める際に有用である。

Declaration Merging

TypeScriptでは、同名の interface を複数回宣言すると、それらが自動的にマージされる仕組みがある。
この機能を Declaration Merging (宣言マージ) と呼ぶ。

 interface Foo {
    x: number;
 }
 
 interface Foo {
    y: number;
 }
 
 let a: Foo;
 console.log(a.x + a.y);  // OK : xとyの両方にアクセス可能


同名のプロパティが異なる型で宣言された場合はコンパイルエラーとなる。

 interface Foo {
    x: number;
 }
 
 interface Foo {
    x: string;  // エラー : xがnumberとstringで矛盾する
 }


type エイリアスでは、Declaration Mergingはできない。
同名の type を再宣言しようとするとコンパイルエラーとなる。

 type Bar = { x: number };
 type Bar = { y: number };   // エラー: 識別子 'Bar' が重複している


下表に、Declaration Mergingの主な用途を示す。

Declaration Mergingの主な用途
用途 説明
ライブラリの型定義を拡張する場合 外部ライブラリが提供する interface に独自プロパティを追加する。
グローバルオブジェクトに新しいプロパティを追加する場合 WindowNodeJS.ProcessEnv 等のグローバルinterfaceを拡張する。
モジュールの型を補完する場合 モジュールオーグメンテーション (Module Augmentation) で既存の型定義を補強する。


Declaration Mergingは、ライブラリ開発や型定義の拡張において有用な機能であるが、同じスコープ内での多用はコードの可読性を低下させる可能性があるため、
使用する場面を適切に判断することが重要である。


関連情報