TypeScript / LESSON 11 / TS11

TypeScript:配列操作と型

1学習の目的

2基礎解説

メソッド型の変化注意点
mapT[] → U[]戻り値の型で決まる
filterT[] → T[]型は変わらない
reduceT[] → 任意初期値で型が決まる
findT[] → T | undefined必ず undefined を考慮
✅ 覚えるべき重要ポイント
  1. map はコールバックの戻り値で型が決まるitems.map(i => i.name) なら string[] になる。型注釈を書かなくても正しく追跡される。
  2. filter は型を変えない。件数は減るが要素の型はそのまま。null を除いても (string | null)[] のままなので、型ガードが必要になる。
  3. reduce は初期値から型が決まる。オブジェクトを組み立てるときは reduce<Record<string, number>> のように型引数を明示する。
  4. find の戻り値は必ず T | undefined。見つからない可能性があるので、そのままプロパティにアクセスするとエラーになる(TS08の ?. が活きる)。
  5. sort は元の配列を書き換える(JS06と同じ)。元を残したいなら [...items].sort(…) とコピーしてから並べ替える。

現場使用例:一覧の絞り込み、集計、ランキング作成、データの整形。実務のデータ処理はほぼすべてこの4つのメソッドの組み合わせで書ける

type Product = { name: string; price: number; category: string };

const items: Product[] = [
  { name: "ノートPC", price: 128000, category: "PC" },
  { name: "マウス", price: 3200, category: "周辺機器" },
];

// map:Product[] → string[] に変わる
const names = items.map((i) => i.name);      // string[]

// filter:型は Product[] のまま
const cheap = items.filter((i) => i.price < 10000);   // Product[]

// reduce:初期値 0 なので number
const total = items.reduce((sum, i) => sum + i.price, 0);

// reduce でオブジェクトを作る(型引数を明示する)
const byCategory = items.reduce<Record<string, number>>(
  (acc, i) => {
    acc[i.category] = (acc[i.category] ?? 0) + 1;
    return acc;
  },
  {}
);

// find は undefined になりうる
const found = items.find((i) => i.name === "存在しない");
console.log(found?.name ?? "未発見");

// ⚠ filter だけでは null が消えない(型の上では)
const mixed: (string | null)[] = ["a", null, "b"];
const bad = mixed.filter((v) => v !== null);   // (string | null)[] のまま

// ✅ 型ガードを書けば string[] になる
const good = mixed.filter((v): v is string => v !== null);   // string[]

3基本ドリル(10問)

TS11-D01 商品配列(ノートPC/128000、マウス/3200)から名前だけを取り出し、カンマ区切りで出力せよ。 コーディング★☆☆無料
期待される結果

出力 == ノートPC, マウス

ヒント

map で name を取り出す。結果は string[] になるので join できる。

模範解答
type Product = { name: string; price: number };

const items: Product[] = [
  { name: "ノートPC", price: 128000 },
  { name: "マウス", price: 3200 },
];

console.log(items.map((i) => i.name).join(", "));
解説

map で型が Product[] から string[] に変わる。だから join が使える——もし数値を返していたら number[] になり、join の挙動も変わる。

TS11-D02 Product[]filter を使うと、結果の型はどうなるか。
A. 変わらず Product[] B. 別の型になる C. any[] D. unknown[]
選択★☆☆無料
期待される結果

解答 == A

ヒント

filter は要素を減らすだけで、変換はしない。

模範解答
A
解説

filter は件数を減らすが型は変えない。だから null を除外しても、型の上では null が残ったままになる——ここが落とし穴。

TS11-D03 商品配列(ノートPC/128000、マウス/3200、キーボード/8500)から10000円未満の商品を絞り込み、件数を出力せよ。 コーディング★☆☆無料
期待される結果

出力 == 2

ヒント

filter で条件に合うものだけを残し、length で数える。

模範解答
type Product = { name: string; price: number };

const items: Product[] = [
  { name: "ノートPC", price: 128000 },
  { name: "マウス", price: 3200 },
  { name: "キーボード", price: 8500 },
];

console.log(items.filter((i) => i.price < 10000).length);
解説

filter の中の i は Product と推論されるi.pric のような打ち間違いは、その場でエラーになる。

TS11-D04 配列の合計を求めるコードを完成させよ。 穴埋め★☆☆無料
コード
const prices = [128000, 3200, 8500];

const total = prices.____((sum, p) => sum + p, 0);

console.log(total);
期待される結果

出力 == 139700

ヒント

配列を1つの値にまとめるメソッド。小文字6文字。

模範解答
reduce
解説

初期値の 0 が number なので、結果も number と推論される。初期値を "" にすれば文字列の連結になる。

TS11-D05 items.find(…) の戻り値の型はどれか。
A. T B. T | undefined C. T[] D. T | null
選択★☆☆無料
期待される結果

解答 == B

ヒント

見つからない場合がある。

模範解答
B
解説

find は見つからないと undefined を返す。そのまま .name と書くとエラーになるので、?. や条件分岐が必要(TS08)。

TS11-D06 商品配列からカテゴリごとの件数を reduce で集計し、「PC:1, 周辺機器:2」の形式で出力せよ(ノートPC/PC、マウス/周辺機器、キーボード/周辺機器)。 コーディング★★☆無料
期待される結果

出力 == PC:1, 周辺機器:2

ヒント

reduce> と型引数を明示する。初期値は {}。

模範解答
type Product = { name: string; category: string };

const items: Product[] = [
  { name: "ノートPC", category: "PC" },
  { name: "マウス", category: "周辺機器" },
  { name: "キーボード", category: "周辺機器" },
];

const counts = items.reduce<Record<string, number>>((acc, i) => {
  acc[i.category] = (acc[i.category] ?? 0) + 1;
  return acc;
}, {});

console.log(
  Object.entries(counts)
    .map(([k, v]) => `${k}:${v}`)
    .join(", ")
);
解説

型引数を書かないと、初期値の {} から「空オブジェクト」と推論されてエラーになる。reduce でオブジェクトを組み立てるときは型引数の明示が定石。

TS11-D07 null を除外して string[] にするコードを完成させよ。 穴埋め★★☆無料
コード
const mixed: (string | null)[] = ["a", null, "b"];

const strs = mixed.filter((v): v ____ string => v !== null);

console.log(strs.join(","));
期待される結果

出力 == a,b

ヒント

型ガード関数の書き方(TS09)。2文字のキーワード。

模範解答
is
解説

これを書かないと (string | null)[] のまま。null を除いたつもりでも型が変わらないので、join の前にエラーになることがある。

TS11-D08 items.sort(…) の副作用として正しいものはどれか。
A. 何も起きない B. 元の配列が並べ替えられる C. 新しい配列が返るだけ D. エラーになる
選択★★☆無料
期待される結果

解答 == B

ヒント

sort は破壊的メソッド(JS06)。

模範解答
B
解説

元の配列が書き換えられる。元の順序を保ちたいなら [...items].sort(…) とコピーしてから並べ替える。

TS11-D09 商品配列を価格の高い順に並べ替え、元の配列は変更せずに1位の商品名を出力せよ(ノートPC/128000、マウス/3200、モニター/45000)。 コーディング★★☆無料
期待される結果

出力 == ノートPC

ヒント

[...items].sort((a, b) => b.price - a.price) とコピーしてから並べ替える。

模範解答
type Product = { name: string; price: number };

const items: Product[] = [
  { name: "ノートPC", price: 128000 },
  { name: "マウス", price: 3200 },
  { name: "モニター", price: 45000 },
];

const sorted = [...items].sort((a, b) => b.price - a.price);

console.log(sorted[0].name);
解説

スプレッド構文でコピーを作るのが安全な作法。元の配列を並べ替えてしまうと、他の場所での処理に影響が出る。

TS11-D10 商品配列から、10万円を超える商品があるか全商品が1000円以上かを判定し、2行出力せよ(ノートPC/128000、マウス/3200)。 コーディング★★☆無料
期待される結果

出力2行。true / true

ヒント

some と every を使う(JS09)。どちらも boolean を返す。

模範解答
type Product = { name: string; price: number };

const items: Product[] = [
  { name: "ノートPC", price: 128000 },
  { name: "マウス", price: 3200 },
];

console.log(items.some((i) => i.price > 100000));
console.log(items.every((i) => i.price >= 1000));
解説

some は「1つでもあれば true」、every は「全部満たせば true」。条件チェックを簡潔に書ける。

4実践シナリオ(5問)

TS11-S01 【売上ランキング】商品配列(名前・単価・数量)から売上を計算し、売上の多い順に「1位 ノートPC:384,000円」の形式で3行出力せよ(ノートPC/128000/3、マウス/3200/15、モニター/45000/1)。 コーディング★★☆無料
期待される結果

出力3行が完全一致

ヒント

map で売上を含む配列を作り、sort で並べ替え、forEach で番号付きで出力する。

模範解答
type Item = { name: string; price: number; qty: number };

const items: Item[] = [
  { name: "ノートPC", price: 128000, qty: 3 },
  { name: "マウス", price: 3200, qty: 15 },
  { name: "モニター", price: 45000, qty: 1 },
];

const sales = 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 で新しい形のオブジェクトに変換している{ name: string; total: number }[] という型が自動で推論されるので、sort の中でも a.total が使える。

TS11-S02 【カテゴリ別の集計】商品配列からカテゴリごとの合計金額を reduce で集計し、「PC:173,000円 / 周辺機器:11,700円」の形式で出力せよ(ノートPC/PC/128000、マウス/周辺機器/3200、モニター/PC/45000、キーボード/周辺機器/8500)。 コーディング★★☆無料
期待される結果

出力 == PC:173,000円 / 周辺機器:11,700円

ヒント

reduce> で金額を加算していく。

模範解答
type Product = { name: string; category: string; price: number };

const items: Product[] = [
  { name: "ノートPC", category: "PC", price: 128000 },
  { name: "マウス", category: "周辺機器", price: 3200 },
  { name: "モニター", category: "PC", price: 45000 },
  { name: "キーボード", category: "周辺機器", price: 8500 },
];

const totals = items.reduce<Record<string, number>>((acc, i) => {
  acc[i.category] = (acc[i.category] ?? 0) + i.price;
  return acc;
}, {});

console.log(
  Object.entries(totals)
    .map(([k, v]) => `${k}:${v.toLocaleString()}円`)
    .join(" / ")
);
解説

SQL07のGROUP BYと同じことをしている?? 0 で初回の未定義に対処するのは、SQL05のCOALESCEと同じ発想。

TS11-S03 【欠損データの除去】(number | null)[] の配列 [100, null, 200, null, 300] から null を型ガードで除去し、合計を出力せよ。 コーディング★★☆無料
期待される結果

出力 == 600

ヒント

filter((v): v is number => v !== null) と書く。これで number[] になり reduce できる。

模範解答
const values: (number | null)[] = [100, null, 200, null, 300];

const numbers = values.filter((v): v is number => v !== null);
const total = numbers.reduce((sum, n) => sum + n, 0);

console.log(total);
解説

型ガードを書かないと (number | null)[] のままで、reduce の中で sum + n がエラーになる。null が混ざる可能性を型が覚えているため。

TS11-S04 【在庫レポート】商品配列(名前・在庫)から、次の3行を出力せよ。
「全4件/在庫あり3件」
「要発注:ノートPC(5), モニター(7)」(在庫10未満)
「在庫総数:54個」
データ:ノートPC/5、マウス/42、モニター/7、サンプル/0
コーディング★★★無料
期待される結果

出力3行が完全一致

ヒント

filter で在庫ありと要発注を分ける。合計は reduce。

模範解答
type Item = { name: string; stock: number };

const items: Item[] = [
  { name: "ノートPC", stock: 5 },
  { name: "マウス", stock: 42 },
  { name: "モニター", stock: 7 },
  { name: "サンプル", stock: 0 },
];

const inStock = items.filter((i) => i.stock > 0);
const lowStock = items.filter((i) => i.stock > 0 && i.stock < 10);
const total = items.reduce((sum, i) => sum + i.stock, 0);

console.log(`全${items.length}件/在庫あり${inStock.length}件`);
console.log(
  `要発注:${lowStock.map((i) => `${i.name}(${i.stock})`).join(", ")}`
);
console.log(`在庫総数:${total}個`);
解説

同じ配列に対して複数の集計を行う典型パターン。filter は元の配列を変えないので、何度でも別の条件で絞り込める。

TS11-S05 【検索と既定値】商品配列から名前で検索する関数 findByName を作り、見つかった場合と見つからない場合の2通りで「マウス:3,200円」「該当なし」と2行出力せよ。 コーディング★★★無料
期待される結果

出力2行。マウス:3,200円 / 該当なし

ヒント

find の戻り値は Product | undefined。?. と ?? を組み合わせる(TS08)。

模範解答
type Product = { name: string; price: number };

const items: Product[] = [
  { name: "ノートPC", price: 128000 },
  { name: "マウス", price: 3200 },
];

function findByName(name: string): Product | undefined {
  return items.find((i) => i.name === name);
}

for (const name of ["マウス", "存在しない商品"]) {
  const found = findByName(name);
  console.log(
    found ? `${found.name}:${found.price.toLocaleString()}円` : "該当なし"
  );
}
解説

find の戻り値を素直に | undefined で表現している。呼び出し側は必ず存在チェックを迫られるので、確認漏れが起きない。

5仕上げ課題

TS11-FINAL 【売上分析レポート】配列操作を組み合わせて、売上分析を実装せよ。

データ:注文配列
type Order = { product: string; category: string; price: number; qty: number };
・ノートPC / PC / 128000 / 2
・マウス / 周辺機器 / 3200 / 10
・モニター / PC / 45000 / 1
・キーボード / 周辺機器 / 8500 / 3

処理
① 各注文の売上(price × qty)を計算
② 売上の多い順に並べ替え(元の配列は変更しない
③ カテゴリ別の売上を集計(reduce)
④ 5万円以上の注文だけを抽出

期待される出力(8行)
=== 売上ランキング ===
1位 ノートPC:256,000円
2位 モニター:45,000円
3位 マウス:32,000円
4位 キーボード:25,500円
--- カテゴリ別 ---
PC:301,000円 / 周辺機器:57,500円
5万円以上の注文:1件
期待される結果

出力8行が完全一致

ヒント

map で売上を含む配列を作り、[...].sort() で並べ替える。カテゴリ別は reduce>。5万円以上は filter で数える。

模範解答
type Order = {
  product: string;
  category: string;
  price: number;
  qty: number;
};

const orders: Order[] = [
  { product: "ノートPC", category: "PC", price: 128000, qty: 2 },
  { product: "マウス", category: "周辺機器", price: 3200, qty: 10 },
  { product: "モニター", category: "PC", price: 45000, qty: 1 },
  { product: "キーボード", category: "周辺機器", price: 8500, qty: 3 },
];

const sales = orders.map((o) => ({
  product: o.product,
  category: o.category,
  amount: o.price * o.qty,
}));

console.log("=== 売上ランキング ===");

const ranked = [...sales].sort((a, b) => b.amount - a.amount);

ranked.forEach((s, index) => {
  console.log(`${index + 1}位 ${s.product}:${s.amount.toLocaleString()}円`);
});

console.log("--- カテゴリ別 ---");

const byCategory = sales.reduce<Record<string, number>>((acc, s) => {
  acc[s.category] = (acc[s.category] ?? 0) + s.amount;
  return acc;
}, {});

console.log(
  Object.entries(byCategory)
    .map(([k, v]) => `${k}:${v.toLocaleString()}円`)
    .join(" / ")
);

const large = sales.filter((s) => s.amount >= 50000);
console.log(`5万円以上の注文:${large.length}件`);
解説

4つの配列メソッドが、それぞれの役割で連携している。map で形を変え、sort で順序を決め、reduce でまとめ、filter で絞る——JS09で学んだ操作が、型に守られたまま使えている。

とくに map の結果に注目してほしい。{ product: string; category: string; amount: number }[] という新しい型が自動的に生まれている。以降の sort や reduce の中で s.amount と書けるのは、この推論のおかげだ。s.amout と打ち間違えれば即座に赤線が出る。

[...sales].sort(…) のコピーも重要だ。もし sales.sort(…) と書いていたら、その後の reducefilter並べ替え済みの配列を処理することになる。今回は結果が変わらないが、「元データを壊さない」という原則を守るほうが、後から機能を足すときに安全になる。

次章ではクラスを学ぶ。JS14で学んだクラスに型が加わると、より厳密な設計ができるようになる。