TypeScript:配列とオブジェクトの型
1学習の目的
- 配列とオブジェクトに型を付けられるようになる。1つの値だけでなく、データ構造そのものを型で表現できるようになる。
- 存在しないプロパティへのアクセスが実行前にエラーになることを体験する。JS07で悩まされた undefined の混入を、書いた時点で防げるようになる。
2基礎解説
| やりたいこと | 書き方 | 意味 |
|---|---|---|
| 数値の配列 | const a: number[] = [1, 2] | 中身は全部数値 |
| オブジェクト | { name: string; price: number } | プロパティごとに型 |
| オブジェクトの配列 | { name: string }[] | 実務で最も多い形 |
| 変更禁止の配列 | readonly number[] | push などができなくなる |
- 配列は「型名 + []」で表す。number[] は「数値だけが入る配列」。違う型を混ぜようとするとその場でエラーになる。
- オブジェクトの型は、プロパティ名と型を ; で区切って並べる。{ name: string; price: number } のように書く。
- 定義していないプロパティにはアクセスできない。product.pric のようなつづり間違いが、実行前にエラーとして分かる。JS07で undefined に悩まされた問題が、ここで解決する。
- プロパティが足りなくてもエラーになる。必須の項目を書き忘れたまま実行してしまうことが無くなる。
- 実務で最も多いのはオブジェクトの配列({ name: string; price: number }[])。APIから受け取るデータも、この形がほとんど。
現場使用例:商品一覧、ユーザー情報、注文明細、APIレスポンス。「複数の項目を持つデータが、複数件ある」という構造は、あらゆるシステムの基本になる。
// 配列の型
const prices: number[] = [128000, 3200, 8500];
const names: string[] = ["ノートPC", "マウス"];
// ❌ 違う型は混ぜられない
const wrong: number[] = [1, "2"];
// → Type 'string' is not assignable to type 'number'.
// オブジェクトの型
const product: { name: string; price: number } = {
name: "ノートPC",
price: 128000,
};
console.log(product.name);
// ❌ 存在しないプロパティはエラー
console.log(product.stock);
// → Property 'stock' does not exist on type ...
// オブジェクトの配列(実務で最も多い形)
const items: { name: string; price: number }[] = [
{ name: "ノートPC", price: 128000 },
{ name: "マウス", price: 3200 },
];
// 配列のメソッドはJavaScriptと同じ(JS09)
const total: number = items.reduce((sum, i) => sum + i.price, 0);
console.log(total.toLocaleString());
// 変更を禁止する
const fixed: readonly number[] = [1, 2, 3];
// fixed.push(4); → エラーになる
3基本ドリル(10問)
出力 == 3
const prices: number[] = [...] と書く。要素数は .length。
const prices: number[] = [128000, 3200, 8500]; console.log(prices.length);
配列の型は「中身の型 + []」。この配列には数値しか入らないことが、型として保証される。
A. そのまま動く B. 実行前にエラーになる C. "2" が数値に変換される D. 実行時に落ちる 選択★☆☆無料
解答 == B
number[] に文字列は含まれるか。
B
配列の中身も型チェックされる。JavaScriptなら何でも混ぜられるが、TypeScriptでは書いた時点で止められる。
出力 == ノートPC:128,000円
{ name: string; price: number } という型を書く。プロパティは name.price ではなく product.name のように取り出す。
const product: { name: string; price: number } = {
name: "ノートPC",
price: 128000,
};
console.log(`${product.name}:${product.price.toLocaleString()}円`);
オブジェクトの型はプロパティごとに指定する。セミコロンで区切って並べるのが基本の書き方。
const names: ____ = ["A", "B", "C"];
console.log(names.join("/"));
出力 == A/B/C
文字列の配列を表す型。string に角カッコを付ける。
string[]
Array<string> という書き方もあるが、実務では string[] のほうが一般的。
A. undefined が返る B. 実行前にエラーになる C. null が返る D. 自動的に追加される 選択★☆☆無料
解答 == B
型に無いものは存在しないと見なされる。
B
JavaScriptなら undefined が返って静かに壊れる場面(JS07)。TypeScriptは書いた時点で止めてくれるので、つづり間違いも即座に分かる。
出力 == 合計:131,200円
型は { name: string; price: number }[] と書く。合計は reduce(JS09)。
const items: { name: string; price: number }[] = [
{ name: "ノートPC", price: 128000 },
{ name: "マウス", price: 3200 },
];
const total: number = items.reduce((sum, i) => sum + i.price, 0);
console.log(`合計:${total.toLocaleString()}円`);
これが実務で最も多いデータ構造。型を付けておくと、i.price と書いた時点でエディタが候補を出してくれる。
const fixed: ____ number[] = [1, 2, 3]; console.log(fixed.length);
出力 == 3
「読み取り専用」を意味する8文字のキーワード。
readonly
readonly を付けると push や代入ができなくなる。設定値やマスタデータなど、途中で書き換わってほしくないデータに使う。
A. price は undefined になる B. エラーになる C. price は 0 になる D. 問題ない 選択★★☆無料
解答 == B
型で定義したプロパティは必須か。
B
定義したプロパティはすべて必須。書き忘れると実行前にエラーになる。「あってもなくてもよい」項目には別の書き方がある(TS08)。
出力 == ノートPC
filter で絞り、map で名前を取り出す(JS09)。型は { name: string; price: number }[]。
const items: { name: string; price: number }[] = [
{ name: "ノートPC", price: 128000 },
{ name: "マウス", price: 3200 },
{ name: "キーボード", price: 8500 },
];
const expensive: string[] = items
.filter((i) => i.price >= 10000)
.map((i) => i.name);
console.log(expensive.join(", "));
filter と map の結果にも型が付く。map((i) => i.name) の結果は自動的に string[] と推論されるので、型注釈と食い違えばエラーになる。
出力 == 注文1:2点
型は { id: number; items: string[] } のように、配列を含めて書ける。
const order: { id: number; items: string[] } = {
id: 1,
items: ["ノートPC", "マウス"],
};
console.log(`注文${order.id}:${order.items.length}点`);
オブジェクトの中に配列を含められる。実務のデータは入れ子になっていることが多く、型もその構造をそのまま表現する。
4実践シナリオ(5問)
出力3行が完全一致
3つのプロパティを持つ型を書き、forEach か for...of で回す。
const items: { name: string; price: number; stock: number }[] = [
{ name: "ノートPC", price: 128000, stock: 5 },
{ name: "マウス", price: 3200, stock: 42 },
{ name: "キーボード", price: 8500, stock: 18 },
];
for (const item of items) {
console.log(`${item.name}:${item.price.toLocaleString()}円(在庫${item.stock}個)`);
}
ループの中でも型が効いている。item の型は自動的に推論されるので、item.stok のような打ち間違いはその場で赤線になる。
出力 == 在庫総額:927,400円
reduce で price * stock を足し込む。
const items: { name: string; price: number; stock: number }[] = [
{ name: "ノートPC", price: 128000, stock: 5 },
{ name: "マウス", price: 3200, stock: 42 },
{ name: "キーボード", price: 8500, stock: 18 },
];
const totalValue: number = items.reduce(
(sum, i) => sum + i.price * i.stock,
0
);
console.log(`在庫総額:${totalValue.toLocaleString()}円`);
reduce の初期値 0 が number なので、結果も number と推論される。型注釈と一致しなければエラーになるので、計算ミスに気づきやすい。
出力 == 要発注:ノートPC(5), キーボード(18)
filter で絞り、map で「名前(在庫)」の文字列を作って join する。
const items: { name: string; price: number; stock: number }[] = [
{ name: "ノートPC", price: 128000, stock: 5 },
{ name: "マウス", price: 3200, stock: 42 },
{ name: "キーボード", price: 8500, stock: 18 },
];
const lowStock: string[] = items
.filter((i) => i.stock < 20)
.map((i) => `${i.name}(${i.stock})`);
console.log(`要発注:${lowStock.join(", ")}`);
filter → map → join という流れは実務で頻出。途中の型が {...}[] から string[] へ変わっていく様子が、型注釈から読み取れる。
「田中商事(東京)」
「ノートPC × 2」
「マウス × 5」 コーディング★★★無料
出力3行が完全一致
型は { name: string; area: string; orders: { product: string; qty: number }[] } のように入れ子で書く。
const customer: {
name: string;
area: string;
orders: { product: string; qty: number }[];
} = {
name: "田中商事",
area: "東京",
orders: [
{ product: "ノートPC", qty: 2 },
{ product: "マウス", qty: 5 },
],
};
console.log(`${customer.name}(${customer.area})`);
for (const order of customer.orders) {
console.log(`${order.product} × ${order.qty}`);
}
型は入れ子にできる。ただしこの書き方は長くなりすぎるのが弱点で、TS07で学ぶ type や interface を使うと、名前を付けて整理できる。
出力3行が完全一致
map で売上を含む新しい配列を作り、sort で並べ替える(JS06の比較関数に注意)。
const items: { name: string; price: number; qty: number }[] = [
{ name: "ノートPC", price: 128000, qty: 3 },
{ name: "マウス", price: 3200, qty: 15 },
{ name: "モニター", price: 45000, qty: 1 },
];
const sales: { name: string; total: number }[] = items
.map((i) => ({ name: i.name, total: i.price * i.qty }))
.sort((a, b) => b.total - a.total);
sales.forEach((s, index) => {
console.log(`${index + 1}位 ${s.name}:${s.total.toLocaleString()}円`);
});
map で作った新しいオブジェクトの型も、型注釈と一致しなければエラーになる。(i) => ({ ... }) の丸カッコを忘れる罠(JS09)も、TypeScriptなら型が合わずに気づける。
5仕上げ課題
データ(すべて型注釈を付けて宣言すること)
products:商品の配列
・{ id: 1, name: "ノートPC", price: 128000 }
・{ id: 2, name: "マウス", price: 3200 }
・{ id: 3, name: "キーボード", price: 8500 }
orders:注文の配列
・{ productId: 1, qty: 2 }
・{ productId: 2, qty: 10 }
・{ productId: 3, qty: 3 }
処理
① 各注文について、商品マスタから対応する商品を探す(find を使う)
② 見つからない場合は「商品不明」として金額0で扱う
③ 明細を出力し、最後に合計を出す
期待される出力(4行)
ノートPC × 2 = 256,000円
マウス × 10 = 32,000円
キーボード × 3 = 25,500円
合計:313,500円
出力4行が完全一致
find は見つからないと undefined を返すため、そのまま product.price と書くとエラーになる。三項演算子で「見つかった場合」と「見つからない場合」を分けて処理する。合計は各明細の金額を足し込む。
const products: { id: number; name: string; price: number }[] = [
{ id: 1, name: "ノートPC", price: 128000 },
{ id: 2, name: "マウス", price: 3200 },
{ id: 3, name: "キーボード", price: 8500 },
];
const orders: { productId: number; qty: number }[] = [
{ productId: 1, qty: 2 },
{ productId: 2, qty: 10 },
{ productId: 3, qty: 3 },
];
let total: number = 0;
for (const order of orders) {
const product = products.find((p) => p.id === order.productId);
const productName: string = product ? product.name : "商品不明";
const amount: number = product ? product.price * order.qty : 0;
total += amount;
console.log(`${productName} × ${order.qty} = ${amount.toLocaleString()}円`);
}
console.log(`合計:${total.toLocaleString()}円`);
この課題の核心は find の戻り値だ。配列から条件に合う要素を探す find は、見つからない可能性があるため、戻り値の型は「商品 または undefined」になる。
だから product.price といきなり書くと、TypeScriptは 「undefined かもしれないものからプロパティを読もうとしている」とエラーを出す。JavaScriptなら実行して初めて Cannot read properties of undefined で落ちていた場面だ(JS07で何度も遭遇したはず)。
ここでは product ? … : … と三項演算子で場合分けすることで、TypeScriptに「undefined の場合はこう扱う」と伝えている。これを書かないとコンパイルが通らない——つまり、型システムが「例外処理を書き忘れること」自体を防いでいる。
この「あるかもしれない・ないかもしれない」の扱いは、TypeScriptで最も重要なテーマのひとつだ。TS08とTS09で、より洗練された書き方を学んでいく。
次章では関数に型を付ける。引数と戻り値を明示することで、関数の使い方そのものが型として表現できるようになる。