TypeScript のオブジェクト型
JavaScript では、オブジェクトが最も一般的に使用されるデータ構造です。TypeScript のオブジェクト型を使用すると、「オブジェクトがどのようなプロパティを持つべきか、またそれらの型は何か」を正確に記述することができます。これが、TypeScript の構造化型システムの核心となっています。
1. オブジェクト型の基礎
(1) 匿名オブジェクト型
最も単純なアプローチ――型宣言でオブジェクトの構造を直接指定する方法:
let user: { name: string; age: number } = {
name: "Charlie",
age: 20
};
(2) 型推論
オブジェクトリテラルが変数に代入されると、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) 複数行構文
オブジェクトのプロパティが多数ある場合は、複数行形式(セミコロンまたはコンマで区切る)を使用することをお勧めします:
let product: {
id: number;
name: string;
price: number;
inStock: boolean;
} = {
id: 1,
name: "TypeScriptAuthoritative Guide",
price: 89.9,
inStock: true
};
▶ 例:関数パラメータのオブジェクト型
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"
}));
出力:
Charlie,20 years old,Email:xiaoming@example.com
2. オプションのプロパティ
? を使用して、オプションの属性を指定します。この属性は省略可能です:
(1) 構文
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 が含まれます:
// 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) オプション属性へのアクセスに関する安全な方法
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 タグを使用して、プロパティを読み取り専用として指定します。これにより、割り当て後はそのプロパティを変更できなくなります:
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 は、浅い読み取り専用修飾子です
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課では、Readonly、Partial、Required およびその他のツールタイプについて解説します。
4. インデックス署名
オブジェクトのプロパティ名は不明だが、プロパティ値の型がわかっている場合は、インデックスシグネチャを使用します:
(1) 文字列インデックス署名
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) デジタルインデックス署名
let arr: { [index: number]: string } = {
0: "first",
1: "second",
2: "third"
};
console.log(arr[1]); // "second"
(3) インデックス署名の制限
インデックスシグネチャでは、すべての既知の属性の型が、インデックス値の型のサブタイプでなければならないことが求められます:
// ❌ 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以降)
let css: { [key: `--${string}`]: string } = {
"--primary-color": "#4A90D9",
"--font-size": "14px"
};
▶ 例:インデックス署名を用いた動的設定の実装
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}`);
出力:
Environment:development
Port:3000
Debugging:true
5. ネストされたオブジェクト型
実際の開発では、オブジェクトが複数のレベルでネストされていることがよくありますが、TypeScript では、型を層ごとに記述することができます:
(1) ネストされたオブジェクトリテラルの種類
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) マルチレベルネスト
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"
6. オブジェクト型の2つのスタイル
(1) 匿名型(インライン)
function greet(user: { name: string; age: number }): string {
return `Hello,${user.name}!`;
}
適しているもの:一度しか使用されない単純な型、関数の引数
(2) 型の命名(インターフェース/型)
interface User {
name: string;
age: number;
}
function greet(user: User): string {
return `Hello,${user.name}!`;
}
適応対象:多用途、多数の特性、および継承や拡張が必要な状況
7. 構造化型システム
TypeScript は構造化された型システムを採用しています。オブジェクトの構造が型の要件を満たしている限り、型名が一致しているかどうかに関係なく、互換性があります:
(1) 構造的マッチング
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 は「冗長なプロパティチェック」を実行します。つまり、対象の型に存在しないプロパティは許可されません:
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 のプロパティではありません。スペルを間違えていませんか?
▶ 例:構造化型の実際の活用例
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"
❓ よくある質問
interface と type のどちらのエイリアスを使うべきですか?interfaceはオブジェクトの形状を定義する場合(宣言の統合やextendsの継承をサポート)に適しており、typeはユニオン型や条件付き型などの複雑な操作に適しています。チーム内で一貫したアプローチを採用していれば、どちらを使用しても問題ありません。詳細な比較については、この章の後半で説明します。初心者はinterfaceから始めることをお勧めします。undefined ユニオン型の違いは何ですか?age?: number ではプロパティが存在しない(完全に欠落している)ことが許容されますが、age: number | undefined ではプロパティが存在することが必須であり、その値は undefined である可能性があります。実際の開発では、オプションのプロパティの方がより一般的に使用されています。プロパティを省略する方が、明示的に undefined と記述するよりも自然だからです。readonly 属性を持つ値は、本当に不変なのでしょうか?readonly はコンパイル時のチェックに過ぎず、実行時の保護機能はありません。型アサーションや any 型を使用することで、このチェックを回避することが可能です。しかし、これにより「意図しない変更」を事実上防ぐことができます。これが型システムの主目的であり、実行時の保護を提供するのではなく、コンパイル時にエラーを検出することにあります。📖 まとめ
- オブジェクト型の説明:「そのオブジェクトにはどのようなプロパティがあり、それらの型は何か?」
{ propertyName: type }構文を使用してください。 ?はオプションのプロパティを示します。readonlyは読み取り専用プロパティを示します。「readonly」は浅い読み取り専用を示します。- インデックス署名
[key: type]: valuetypeは、「キーの型は指定されていないが、値の型は指定されている」オブジェクトを表します。 - ネストされたオブジェクトは、層ごとに型を記述しています。2~3層を超える場合は、それらを個別のインターフェースや型に抽出することをお勧めします。
- TypeScript は構造型システムを採用しています。構造が一致すれば互換性があります。オブジェクトリテラルを直接代入する際には、冗長なプロパティに対するチェックが行われます。
📝 練習問題
- 基本問題(難易度 ⭐):「本」を表すオブジェクト型を定義し(タイトル、著者、価格、在庫あり?)、在庫ありの本と在庫なしの本の2つのオブジェクトを作成し、それぞれのタイトルと価格を出力してください。
- 上級問題(難易度 ⭐⭐):更新後のユーザーオブジェクトを返す関数
updateUser(user: { name: string; age: number; email?: string }, updates: { age?: number; email?: string })を実装してください。元のユーザーnameは不変でなければなりません(readonlyを使用してください)。 - 課題(難易度 ⭐⭐⭐):インデックスシグネチャを用いて型
CacheStoreを定義してください。ここで、キーは文字列、値は{ data: T; timestamp: number }です(T はジェネリック型です)。CacheStore<string>のインスタンスを作成し、そこに2つのキャッシュエントリを格納した後、それらを順に処理して、キャッシュされたすべてのキーとその有効期限ステータスを出力してください(有効期限は5秒とします)。



