何をしたいユーティリティか:「配列の分割」
ここでの「分割」は、1つの配列を「小さな配列のかたまり」に切り分ける処理のことです。
英語だと「chunk(チャンク)」と呼ばれることが多いです。
例えば、こんな場面で使います。
大量データを 100 件ずつに分けて、順番に API に送信したい。
メール送信対象を 50 件ずつに分けて、バッチ処理したい。
画面表示用に、「1 行あたり 3 件」でカードを並べたい。
「配列の分割ユーティリティ」を持っておくと、こういう処理を毎回 for 文で書かずに済みます。
基本イメージ:「N 個ずつに切り分ける」
まずは結果の形をイメージする
例えば、次の配列があります。
const data = [1, 2, 3, 4, 5, 6, 7];
JavaScriptこれを「3 個ずつ」に分割したいとします。
欲しい結果はこうです。
[
[1, 2, 3],
[4, 5, 6],
[7],
]
JavaScriptつまり、「元の配列」 → 「配列の配列」に変換するイメージです。
この「小さな配列 1 つ 1 つ」が「チャンク(かたまり)」です。
一番基本:固定サイズで分割する chunk 関数
シンプルな実装
まずは、「配列を size 個ずつに分割する」基本のユーティリティを書いてみます。
function chunk(array, size) {
if (!Array.isArray(array)) {
return [];
}
const result = [];
const chunkSize = size > 0 ? size : 1;
for (let i = 0; i < array.length; i += chunkSize) {
const part = array.slice(i, i + chunkSize);
result.push(part);
}
return result;
}
JavaScript重要なポイントをかみ砕いて説明する
chunkSize で「実際に使うサイズ」を決めています。size が 0 やマイナスでも、最低 1 にはしておかないと無限ループになるからです。
ループの i は、「チャンクの先頭インデックス」です。i += chunkSize とすることで、「0 → chunkSize → 2*chunkSize → …」と、チャンクごとの先頭にジャンプしていきます。
array.slice(i, i + chunkSize) で、「i から i + chunkSize まで」を切り出しています。
最後のチャンクは、要素数が chunkSize 未満でもそのまま返ってきます(これがちょうど欲しい挙動です)。
実際の動き
chunk([1, 2, 3, 4, 5, 6, 7], 3);
// [[1, 2, 3], [4, 5, 6], [7]]
chunk([1, 2, 3, 4], 2);
// [[1, 2], [3, 4]]
chunk([1, 2, 3], 10);
// [[1, 2, 3]]
chunk([1, 2, 3], 0);
// size が 0 でも最低 1 になるので [[1], [2], [3]]
JavaScriptこのパターンを覚えておけば、「N 個ずつに分ける」はいつでも書けます。
実務でよくある使い方の例
例1:API に送るデータを分割する
例えば、API が「一度に 100 件までしか受け付けない」とします。
1000 件のデータを送るには、「100 件ずつに分割して、10 回に分けて送る」必要があります。
const records = /* 1000件の配列 */;
const chunks = chunk(records, 100);
for (const part of chunks) {
await sendToApi(part);
}
JavaScriptここでのポイントは、「分割のロジック」と「送信のロジック」を分離できていることです。chunk が「分けること」だけに集中してくれるので、sendToApi 側は「渡された配列を送る」だけで済みます。
例2:画面でカードを「1 行 3 件」で並べる
UI で「カードを 1 行 3 件で並べたい」ときにも、分割は便利です。
const items = [
/* 商品や記事などの配列 */
];
const rows = chunk(items, 3);
JavaScriptテンプレート側では、「rows をループして、その中の item をさらにループする」という形で、
「行」と「列」の構造を自然に表現できます。
条件で分割する:predicate での分割
「サイズ」ではなく「条件」で分けたい場合
ときどき、「N 個ずつ」ではなく、「条件が変わるところで分割したい」ことがあります。
例えば、ログを「日付が変わるごと」に分割したい。
ステータスが変わるところで区切りたい。
この場合は、「前の要素と比べて条件が変わったら新しいグループを始める」という発想で書きます。
function splitWhen(array, isBoundary) {
if (!Array.isArray(array)) {
return [];
}
const result = [];
let current = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
const prev = i > 0 ? array[i - 1] : undefined;
if (i > 0 && isBoundary(prev, item)) {
result.push(current);
current = [];
}
current.push(item);
}
if (current.length > 0) {
result.push(current);
}
return result;
}
JavaScriptisBoundary(prev, item) が「区切りかどうか」を判定する関数です。true を返したら「ここで一度グループを閉じて、新しいグループを始める」という動きになります。
実際の使い方:日付が変わるところで分割
const logs = [
{ at: "2024-01-01T10:00:00Z", message: "A" },
{ at: "2024-01-01T12:00:00Z", message: "B" },
{ at: "2024-01-02T09:00:00Z", message: "C" },
{ at: "2024-01-02T18:00:00Z", message: "D" },
];
function toDateString(log) {
return log.at.slice(0, 10); // "YYYY-MM-DD"
}
const groups = splitWhen(logs, (prev, curr) => {
return toDateString(prev) !== toDateString(curr);
});
JavaScript結果はこうなります。
[
[
{ at: "2024-01-01T10:00:00Z", message: "A" },
{ at: "2024-01-01T12:00:00Z", message: "B" },
],
[
{ at: "2024-01-02T09:00:00Z", message: "C" },
{ at: "2024-01-02T18:00:00Z", message: "D" },
],
]
JavaScript「日付が変わるところで区切る」というルールを isBoundary に閉じ込めているのがポイントです。
実務で意識してほしい設計のポイント
「分割サイズが 0 やマイナス」のときの扱い
chunk のような関数では、size が 0 やマイナスだと危険です。for (let i = 0; i < length; i += size) が無限ループになってしまうからです。
ユーティリティ側で、
size <= 0 のときは 1 に丸める。
または、空配列を返す。
などのポリシーを決めておくと、安全に使えます。
今回の実装では「最低 1」に丸める方式を採用しました。
「元の配列を壊さない」ことを前提にする
chunk や splitWhen は、基本的に「元の配列をそのままにして、新しい配列を返す」設計にしておくのがおすすめです。
業務コードでは、「元データはそのまま」「変換結果だけを別で持つ」ほうがバグを減らせます。
「分割」と「ページング」の違いを意識する
ページングと分割は似ていますが、目的が少し違います。
ページング
「ページ番号で一部分だけを取り出す」
「1ページ目だけ」「3ページ目だけ」といった使い方。
分割(chunk)
「最初から全部を小分けにしておく」
「全チャンクを順番に処理する」「行ごとに表示する」といった使い方。
内部的にはどちらも slice を使いますが、
「ページ番号でアクセスするか」「配列の配列として全部持つか」という違いを意識しておくと、設計がクリアになります。
少し手を動かして感覚をつかむ
コンソールで、次のようなコードを実際に打ってみてください。
chunk([1, 2, 3, 4, 5, 6, 7], 3);
chunk([1, 2, 3, 4, 5], 2);
chunk([1, 2, 3], 10);
chunk([1, 2, 3], 0);
const logs = [
{ at: "2024-01-01T10:00:00Z", message: "A" },
{ at: "2024-01-01T12:00:00Z", message: "B" },
{ at: "2024-01-02T09:00:00Z", message: "C" },
{ at: "2024-01-02T18:00:00Z", message: "D" },
];
splitWhen(logs, (prev, curr) => prev.at.slice(0, 10) !== curr.at.slice(0, 10));
JavaScript「どういう単位で分割されているか」「最後のかたまりがどう扱われているか」「条件での分割がどう効いているか」を、自分の目で確認してみてください。
そのうえで、自分のプロジェクトに
export function chunk(...) { ... }
export function splitWhen(...) { ... }
JavaScriptのような関数を置き、
「配列を小分けにしたくなったら、必ずこの“分割ユーティリティ”を通す」
というルールを作ってみてください。
そうすると、あなたのコードは、「場当たり的な for 文」から、「意図がはっきりしたデータ分割」に一段ステップアップしていきます。
