404 Not Found

404 Not Found


nginx

TypeScript のオブジェクト型

JavaScript では、オブジェクトが最も一般的に使用されるデータ構造です。TypeScript のオブジェクト型を使用すると、「オブジェクトがどのようなプロパティを持つべきか、またそれらの型は何か」を正確に記述することができます。これが、TypeScript の構造化型システムの核心となっています。

1. オブジェクト型の基礎

(1) 匿名オブジェクト型

最も単純なアプローチ――型宣言でオブジェクトの構造を直接指定する方法:

TYPESCRIPT
let user: { name: string; age: number } = {
  name: "Charlie",
  age: 20
};

(2) 型推論

オブジェクトリテラルが変数に代入されると、TypeScript は自動的に型を推論します:

TYPESCRIPT
let user = {
  name: "Charlie",
  age: 20
};
// Inferred as { name: string; age: number }

user.name = "Diana";   // ✅
user.age = 21;        // ✅
// user.email = "x@x.com";  // ❌ Not included in the types of inferences email Properties

(3) 複数行構文

オブジェクトのプロパティが多数ある場合は、複数行形式(セミコロンまたはコンマで区切る)を使用することをお勧めします:

TYPESCRIPT
let product: {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
} = {
  id: 1,
  name: "TypeScriptAuthoritative Guide",
  price: 89.9,
  inStock: true
};

▶ 例:関数パラメータのオブジェクト型

TYPESCRIPT
function createUser(user: { name: string; age: number; email: string }): string {
  return `${user.name},${user.age} years old,Email:${user.email}`;
}

console.log(createUser({
  name: "Charlie",
  age: 20,
  email: "xiaoming@example.com"
}));

出力:

TEXT
Charlie,20 years old,Email:xiaoming@example.com

2. オプションのプロパティ

? を使用して、オプションの属性を指定します。この属性は省略可能です:

(1) 構文

TYPESCRIPT
let user: { name: string; age?: number } = {
  name: "Charlie"
  // age This information is optional.
};

console.log(user.name);  // "Charlie"
console.log(user.age);   // undefined

(2) オプション属性の種類

オプション属性の種類には、自動的に undefined が含まれます:

TYPESCRIPT
// age?: number Equivalent to age: number | undefined
// But there is an important difference:age?: number The property does not exist.,age: number | undefined The property must exist.

let config: { host: string; port?: number } = { host: "localhost" };
// config.port is undefined(The property does not exist either. ok 's)

let config2: { host: string; port: number | undefined } = { host: "localhost" };
// ❌ Error! port property must exist,Even if undefined You must also explicitly write
// Correct Way to Write It:{ host: "localhost", port: undefined }

(3) オプション属性へのアクセスに関する安全な方法

TYPESCRIPT
interface User {
  name: string;
  age?: number;
}

function getAgeText(user: User): string {
  // ❌ Unsafe——If age Does not exist,undefined.toFixed() It crashes during runtime
  // return user.age.toFixed(0) + " years old";

  // ✅ Method 1:Check first
  if (user.age !== undefined) {
    return user.age.toFixed(0) + " years old";
  }
  return "Age unknown";

  // ✅ Method 2:Provide a default value
  // return (user.age ?? 0).toFixed(0) + " years old";
}

3. 読み取り専用プロパティ

readonly タグを使用して、プロパティを読み取り専用として指定します。これにより、割り当て後はそのプロパティを変更できなくなります:

TYPESCRIPT
let user: { readonly id: number; name: string } = {
  id: 1,
  name: "Charlie"
};

user.name = "Diana";     // ✅ General attributes can be modified
// user.id = 2;         // ❌ Read-only properties cannot be modified.

(2) readonly は、浅い読み取り専用修飾子です

TYPESCRIPT
let config: { readonly data: { value: number } } = {
  data: { value: 42 }
};

// config.data = { value: 100 };  // ❌ data Cannot be reassigned
config.data.value = 100;          // ✅ data The internal properties can still be modified!
💡 完全読み取り専用: オブジェクトのすべてのレベルを読み取り専用にする必要がある場合は、Readonly<type> ツールタイプ(ネスト型)または as const アサーションを使用してください。第19課では、ReadonlyPartialRequired およびその他のツールタイプについて解説します。


4. インデックス署名

オブジェクトのプロパティ名は不明だが、プロパティ値の型がわかっている場合は、インデックスシグネチャを使用します:

(1) 文字列インデックス署名

TYPESCRIPT
let scores: { [key: string]: number } = {
  Chinese Language: 90,
  Mathematics: 95,
  English: 88
};

console.log(scores["Mathematics"]);   // 95

// Add a New Property——As long as it is string key + number Any value will do.
scores["Physics"] = 85;   // ✅

(2) デジタルインデックス署名

TYPESCRIPT
let arr: { [index: number]: string } = {
  0: "first",
  1: "second",
  2: "third"
};

console.log(arr[1]);  // "second"

(3) インデックス署名の制限

インデックスシグネチャでは、すべての既知の属性の型が、インデックス値の型のサブタイプでなければならないことが求められます:

TYPESCRIPT
// ❌ Error: name is string,However, index signatures require that all values be number
// let config: { [key: string]: number; name: string } = { name: "test" };

// ✅ Correct:All value types are consistent
let config: { [key: string]: string; name: string } = { name: "test" };

(4) テンプレートリテラルによるインデックスシグネチャ(TypeScript 4.4以降)

TYPESCRIPT
let css: { [key: `--${string}`]: string } = {
  "--primary-color": "#4A90D9",
  "--font-size": "14px"
};

▶ 例:インデックス署名を用いた動的設定の実装

TYPESCRIPT
interface EnvConfig {
  [key: string]: string | number | boolean;
  // All values can be string、number or boolean
  // Known properties must also be compatible.
  NODE_ENV: string;
  PORT: number;
}

let env: EnvConfig = {
  NODE_ENV: "development",
  PORT: 3000,
  DEBUG: true,        // ✅ Dynamic Properties
  API_URL: "http://localhost:8080"  // ✅ Dynamic Properties
};

console.log(`Environment:${env.NODE_ENV}`);
console.log(`Port:${env.PORT}`);
console.log(`Debugging:${env.DEBUG}`);

出力:

TEXT
Environment:development
Port:3000
Debugging:true

5. ネストされたオブジェクト型

実際の開発では、オブジェクトが複数のレベルでネストされていることがよくありますが、TypeScript では、型を層ごとに記述することができます:

(1) ネストされたオブジェクトリテラルの種類

TYPESCRIPT
let user: {
  name: string;
  address: {
    city: string;
    zip: string;
  };
  tags: string[];
} = {
  name: "Charlie",
  address: {
    city: "Beijing",
    zip: "100000"
  },
  tags: ["Developer", "TypeScript"]
};

console.log(user.address.city);  // "Beijing"
console.log(user.tags[0]);       // "Developer"

(2) マルチレベルネスト

TYPESCRIPT
type ApiResponse = {
  status: number;
  data: {
    user: {
      id: number;
      profile: {
        avatar: string;
        bio: string;
      };
    };
  };
};

let response: ApiResponse = {
  status: 200,
  data: {
    user: {
      id: 1,
      profile: {
        avatar: "https://example.com/avatar.png",
        bio: "TypeScript Enthusiast"
      }
    }
  }
};

console.log(response.data.user.profile.bio);  // "TypeScript Enthusiast"
💡 ヒント: ネストが 2~3 レベルを超えた場合は、可読性と再利用性を高めるために、内部の型を個別のインターフェースや型エイリアスとして抽出することをお勧めします。これについては、レッスン 9~11 で詳しく解説します。


6. オブジェクト型の2つのスタイル

(1) 匿名型(インライン)

TYPESCRIPT
function greet(user: { name: string; age: number }): string {
  return `Hello,${user.name}!`;
}

適しているもの:一度しか使用されない単純な型、関数の引数

(2) 型の命名(インターフェース/型)

TYPESCRIPT
interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return `Hello,${user.name}!`;
}

適応対象:多用途、多数の特性、および継承や拡張が必要な状況

📌 推奨事項: プロパティが 3 つ以上ある場合や、型を再利用する必要がある場合は、それをインターフェースまたは型エイリアスとして抽出してください。単純な 1 回限りのパラメータについては、匿名型を使用してください。


7. 構造化型システム

TypeScript は構造化された型システムを採用しています。オブジェクトの構造が型の要件を満たしている限り、型名が一致しているかどうかに関係なく、互換性があります:

(1) 構造的マッチング

TYPESCRIPT
interface Point {
  x: number;
  y: number;
}

let point: Point = { x: 1, y: 2 };   // ✅ Has x and y

let point3D = { x: 1, y: 2, z: 3 };
point = point3D;   // ✅ point3D has x and y,Redundant attributes do not conflict

(2) 不要な属性がないか確認する

オブジェクトリテラルに値を直接代入する場合、TypeScript は「冗長なプロパティチェック」を実行します。つまり、対象の型に存在しないプロパティは許可されません:

TYPESCRIPT
interface Point {
  x: number;
  y: number;
}

// ❌ Check for Unnecessary Attributes——No additional properties are allowed when directly assigning an object literal.
// let p: Point = { x: 1, y: 2, z: 3 };

// ✅ Method 1:Bypassing via Variables(Because variables do not trigger unnecessary property checks)
let temp = { x: 1, y: 2, z: 3 };
let p: Point = temp;   // ✅

// ✅ Method 2:Using Type Assertions
let p2: Point = { x: 1, y: 2, z: 3 } as Point;

// ✅ Method 3:Extension Interface(Top Recommendations)
interface Point3D extends Point {
  z: number;
}
let p3: Point3D = { x: 1, y: 2, z: 3 };
💡 なぜ余分なプロパティをチェックするのでしょうか? これはスペルミスを検出するためです。{ x: 1, y: 2, zzz: 3 } と記述すると、TypeScript はすぐに警告を表示します。「zzz」は Point のプロパティではありません。スペルを間違えていませんか?

▶ 例:構造化型の実際の活用例

TYPESCRIPT
interface Printable {
  toString(): string;
}

// Date has toString method,Structural Matching
let date: Printable = new Date();

// Custom objects include toString Methods,Also matches
let custom: Printable = {
  toString() { return "Custom Objects"; }
};

function print(obj: Printable): void {
  console.log(obj.toString());
}

print(date);    // Output Date the string representation of
print(custom);  // Output "Custom Objects"

❓ よくある質問

Q オブジェクト型には interfacetype のどちらのエイリアスを使うべきですか?
A ほとんどの場合、両者は同等です。interfaceはオブジェクトの形状を定義する場合(宣言の統合やextendsの継承をサポート)に適しており、typeはユニオン型や条件付き型などの複雑な操作に適しています。チーム内で一貫したアプローチを採用していれば、どちらを使用しても問題ありません。詳細な比較については、この章の後半で説明します。初心者はinterfaceから始めることをお勧めします。
Q オプションのプロパティと undefined ユニオン型の違いは何ですか?
A age?: number ではプロパティが存在しない(完全に欠落している)ことが許容されますが、age: number | undefined ではプロパティが存在することが必須であり、その値は undefined である可能性があります。実際の開発では、オプションのプロパティの方がより一般的に使用されています。プロパティを省略する方が、明示的に undefined と記述するよりも自然だからです。
Q なぜオブジェクトリテラルには追加のプロパティチェックが行われるのに、変数には行われないのですか?
A オブジェクトリテラルに直接値を代入する場合、開発者は通常、自分が何を書いたかを正確に把握しています。余分なプロパティは、ほとんどの場合、タイプミスによるものです。一方、変数に値を代入する場合、その変数の型は別の場所から取得される可能性があり、余分なプロパティが有効である可能性もあります(サブタイプからの追加プロパティなど)。これは、TypeScriptにおける「安全性」と「柔軟性」のトレードオフです。
Q readonly 属性を持つ値は、本当に不変なのでしょうか?
A readonly はコンパイル時のチェックに過ぎず、実行時の保護機能はありません。型アサーションや any 型を使用することで、このチェックを回避することが可能です。しかし、これにより「意図しない変更」を事実上防ぐことができます。これが型システムの主目的であり、実行時の保護を提供するのではなく、コンパイル時にエラーを検出することにあります。

📖 まとめ

📝 練習問題

  1. 基本問題(難易度 ⭐):「本」を表すオブジェクト型を定義し(タイトル、著者、価格、在庫あり?)、在庫ありの本と在庫なしの本の2つのオブジェクトを作成し、それぞれのタイトルと価格を出力してください。
  2. 上級問題(難易度 ⭐⭐):更新後のユーザーオブジェクトを返す関数 updateUser(user: { name: string; age: number; email?: string }, updates: { age?: number; email?: string }) を実装してください。元のユーザー name は不変でなければなりません(readonly を使用してください)。
  3. 課題(難易度 ⭐⭐⭐):インデックスシグネチャを用いて型 CacheStore を定義してください。ここで、キーは文字列、値は { data: T; timestamp: number } です(T はジェネリック型です)。CacheStore<string>のインスタンスを作成し、そこに2つのキャッシュエントリを格納した後、それらを順に処理して、キャッシュされたすべてのキーとその有効期限ステータスを出力してください(有効期限は5秒とします)。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%