こんにちは、かつコーチです。
前回は配列とタプルの型の書き方を解説しました。
今回は、TypeScriptを使ううえで避けて通れない「オブジェクトの型定義」について解説します。
ユーザー情報や商品データなど、実務のコードではオブジェクトを扱う場面がとても多いです。
型をきちんと定義できるようになると、コードの安全性がぐっと上がるので、しっかり押さえていきましょう。
オブジェクトの型とは?
なぜオブジェクトに型が必要なのか
オブジェクト(複数のプロパティをまとめて持つデータのかたまり)は、JavaScriptの段階では自由に形を変えられてしまいます。
たとえば、ある場所では name というプロパティがあると思って書いたコードが、別の場所では name が存在しないオブジェクトを渡されてエラーになる、といったことが起こり得ます。
TypeScriptでオブジェクトの型を定義しておくと、「このオブジェクトにはどんなプロパティがあり、それぞれ何の型なのか」をコード上で保証できます。
存在しないプロパティにアクセスしようとした瞬間に、実行前(コンパイル時)にエラーとして教えてくれるのが大きなメリットです。
インラインで型を書く基本形
もっともシンプルな書き方は、変数の後ろに直接オブジェクトの型を書く方法です。
const user: { name: string; age: number } = {
name: "かつコーチ",
age: 30,
};
console.log(user.name); // "かつコーチ"
{ name: string; age: number } の部分がオブジェクトの型です。
プロパティ名の後ろにコロンを書き、その型を指定します。
区切りはセミコロンでもカンマでも動きますが、複数行で書くときはセミコロンを使うのが一般的です。
type文を使った型定義
type文で名前をつける
インラインで型を書くやり方は、同じ形のオブジェクトを何度も使う場合に不便です。
そこで使うのが type 文です。
type User = {
name: string;
age: number;
};
const user1: User = {
name: "かつコーチ",
age: 30,
};
const user2: User = {
name: "田中",
age: 25,
};
型に User という名前をつけておけば、同じ形のオブジェクトを何度でも再利用できます。
変数の型注釈も : User と書くだけになり、コードがすっきりします。
プロパティが増えたときのメリット
オブジェクトが持つプロパティが増えるほど、type で名前をつけるメリットは大きくなります。
type Product = {
id: number;
name: string;
price: number;
stock: number;
category: string;
};
function displayProduct(product: Product): void {
console.log(`${product.name}:${product.price}円(在庫${product.stock})`);
}
関数の引数に Product 型を指定しておけば、呼び出す側は「どんな形のオブジェクトを渡せばいいか」がすぐに分かります。
チームで開発するときは、この「型を見れば仕様が分かる」という状態がとても重要です。
オプショナルプロパティと読み取り専用プロパティ
? で任意のプロパティにする
すべてのプロパティが必ず入るとは限りません。
「あってもなくてもいい」プロパティには ? をつけます。
type User = {
name: string;
age: number;
email?: string; // 省略可能
};
const user1: User = {
name: "かつコーチ",
age: 30,
}; // emailを省略してもエラーにならない
const user2: User = {
name: "田中",
age: 25,
email: "tanaka@example.com",
};
email?: string と書くと、email プロパティを持たないオブジェクトも User 型として認められます。
省略された場合、user1.email の値は undefined になります。
readonly で書き換えを防ぐ
一度セットしたら変更してほしくないプロパティには、readonly をつけます。
type User = {
readonly id: number;
name: string;
};
const user: User = {
id: 1,
name: "かつコーチ",
};
user.name = "変更後の名前"; // OK
user.id = 2; // エラー:読み取り専用プロパティには代入できない
id のようにデータベースの主キーなど「作成後に変わってはいけない値」には readonly をつけておくと、うっかり書き換えるミスを事前に防げます。
つまずきやすいポイント:プロパティ不足のエラー
実際につまずいた話
私が実務でTypeScriptを使い始めたころ、フォームの入力値をAPIに送信する処理で、こんなエラーに何度も遭遇しました。
エラーメッセージは Property 'email' is missing in type '{ name: string; age: number; }' but required in type 'User'. というものです。
原因は単純で、User 型に必須プロパティとして定義していた email を、送信用のオブジェクトに入れ忘れていたことでした。
最初は「実行してみないと気づけないミスがコンパイル前に見つかった」という感覚がなく、むしろ「なんでこんなに厳しいんだ」と感じていました。
ですが、これがまさにTypeScriptの型チェックの効果です。
実行前にミスを見つけられたおかげで、本番環境に不完全なデータを送ってしまう事故を防げたわけです。
Before/Afterで確認する
❌ Before:必須プロパティを入れ忘れる
type User = {
name: string;
age: number;
email: string;
};
function registerUser(user: User): void {
console.log(`${user.name}さんを登録しました`);
}
// emailが抜けているためコンパイルエラーになる
registerUser({
name: "かつコーチ",
age: 30,
});
このコードは、User 型が要求している email プロパティが渡すオブジェクトに存在しないため、コンパイル時にエラーになります。
✅ After:すべての必須プロパティを渡す
type User = {
name: string;
age: number;
email: string;
};
function registerUser(user: User): void {
console.log(`${user.name}さんを登録しました`);
}
registerUser({
name: "かつコーチ",
age: 30,
email: "katsu@example.com",
});
必須プロパティをすべて渡すことで、エラーが解消されます。
もし本当に省略していい値なら、型定義側で email?: string にするのが正しい対応です。
「エラーが出たから型を緩める」のではなく、「本当に必須なのか、省略可能なのか」を型定義に反映させることを意識しましょう。
応用:インデックスシグネチャで柔軟なオブジェクトを扱う
プロパティ名が事前に分からない場合
APIのレスポンスなどで、プロパティ名が動的に決まるオブジェクトを扱うこともあります。
そういった場合は「インデックスシグネチャ」という書き方を使います。
type Scores = {
[subject: string]: number;
};
const scores: Scores = {
国語: 80,
数学: 90,
英語: 75,
};
console.log(scores.国語); // 80
console.log(scores["理科"]); // undefined(存在しないプロパティ)
[subject: string]: number と書くことで、「プロパティ名は文字列なら何でもよく、値は必ず数値」という型を表現できます。
科目名が事前に決まっていない集計処理などで役立つ書き方です。
まとめ
この記事のポイント
- オブジェクトの型は
{ プロパティ名: 型 }の形で定義する - 同じ形のオブジェクトを繰り返し使うときは
typeで名前をつけると再利用しやすい ?で省略可能なプロパティ、readonlyで読み取り専用のプロパティを表現できる- 必須プロパティの入れ忘れはコンパイル時にエラーとして検出される
- プロパティ名が動的な場合はインデックスシグネチャを使う
次に読むべき記事
オブジェクトの型が扱えるようになったら、次は関数の型について学んでいきましょう。
→ 次の記事:関数の型:引数と戻り値に型をつける