何をしたいユーティリティか:「要素挿入」
「要素挿入」は、配列の好きな位置に新しい要素を差し込む処理です。[1, 2, 4] の「2 と 4 の間に 3 を入れたい」みたいなやつですね。
業務だと、例えばこういう場面でよく出てきます。
フォームの項目を途中に追加したい。
メニューの並びの特定位置に新しいメニューを差し込みたい。
ドラッグ&ドロップで「この位置に挿入」したい。
ここで大事になるのが、
- 元の配列を壊すか、壊さないか(破壊的か非破壊か)
- インデックスが範囲外のときどう扱うか
この2つを、ユーティリティとして“決めておく”ことです。
基本形:非破壊で要素を挿入する insertAt
元の配列を壊さない「安全な挿入」
まずは、元の配列を変更せずに、新しい配列を返すタイプからいきます。
業務では、こちらをデフォルトにしておくのがおすすめです。
function insertAt(array, index, value) {
if (!Array.isArray(array)) {
return [];
}
const len = array.length;
const safeIndex =
index < 0 ? 0 :
index > len ? len :
index;
const head = array.slice(0, safeIndex);
const tail = array.slice(safeIndex);
return [...head, value, ...tail];
}
JavaScript重要ポイントをかみ砕く
まず Array.isArray で配列かどうかをチェックし、違ったら空配列を返します。index がマイナスなら 0 に、長さより大きければ末尾(len)に丸めています。slice(0, safeIndex) で「挿入位置より前」、slice(safeIndex) で「挿入位置以降」を取り出し、[...head, value, ...tail] で「前+挿入する値+後ろ」をつなげて新しい配列を作っています。
元の配列は一切変更されません。
動作例でイメージを固める
真ん中に挿入する
const arr = [1, 2, 4];
const result = insertAt(arr, 2, 3);
// [1, 2, 3, 4]
console.log(arr); // [1, 2, 4](元はそのまま)
console.log(result); // [1, 2, 3, 4]
JavaScriptインデックス 2 の位置(0:1, 1:2, 2:4 の「4 の前」)に 3 が挿入されています。
先頭に挿入する
const arr = [2, 3];
const result = insertAt(arr, 0, 1);
// [1, 2, 3]
JavaScriptindex = 0 のときは、「全部の前に挿入」です。
末尾に挿入する
const arr = [1, 2];
const result = insertAt(arr, 5, 3);
// [1, 2, 3]
JavaScriptindex = 5 は配列の長さ 2 より大きいので、末尾に丸められます。
「範囲外インデックスは末尾扱い」と決めておくと、呼び出し側が楽になります。
マイナスインデックスを先頭扱いにする
const arr = [2, 3];
const result = insertAt(arr, -10, 1);
// [1, 2, 3]
JavaScriptマイナスは 0 に丸めているので、「先頭に挿入」になります。
なぜ非破壊にするのか(業務的な理由)
配列を直接いじる splice を使うと、こう書けます。
const arr = [1, 2, 4];
arr.splice(2, 0, 3); // 破壊的
console.log(arr); // [1, 2, 3, 4]
JavaScriptこれはこれで便利ですが、「元の配列が書き換わる」というのが大きなクセです。
業務コードでは、
- API から来た生データはそのまま保持しておきたい
- React などで「状態は不変に扱いたい」
- 「どこで配列が変わったか」を追いやすくしたい
といった理由から、非破壊で新しい配列を返すスタイルが好まれます。
insertAt のようなユーティリティを使うと、
- 「元の配列は絶対に変わらない」
- 「挿入後の配列は戻り値だけ」
という前提でコードを書けるので、バグが減ります。
応用1:複数要素をまとめて挿入する insertMany
配列ごと差し込むバージョン
「1つの要素」ではなく、「複数の要素(配列)」を挿入したいこともよくあります。
function insertMany(array, index, values) {
if (!Array.isArray(array)) {
return [];
}
if (!Array.isArray(values) || values.length === 0) {
return array.slice();
}
const len = array.length;
const safeIndex =
index < 0 ? 0 :
index > len ? len :
index;
const head = array.slice(0, safeIndex);
const tail = array.slice(safeIndex);
return [...head, ...values, ...tail];
}
JavaScript動作例
const arr = [1, 5];
const result = insertMany(arr, 1, [2, 3, 4]);
// [1, 2, 3, 4, 5]
JavaScript「1 と 5 の間に [2,3,4] を挿入する」というイメージです。
応用2:条件に合う位置の“後ろ”に挿入する
「この要素の直後に挿入したい」をユーティリティにする
業務では、「インデックス指定」よりも「特定の条件に合う要素の後ろに挿入したい」ことが多いです。
function insertAfter(array, predicate, value) {
if (!Array.isArray(array) || typeof predicate !== "function") {
return Array.isArray(array) ? array.slice() : [];
}
const index = array.findIndex(predicate);
if (index === -1) {
return array.slice();
}
return insertAt(array, index + 1, value);
}
JavaScriptここでは、
predicateで「どの要素の後ろか」を決める- 見つからなければ何もせずコピーだけ返す
- 見つかったら、そのインデックスの「1つ後ろ」に
insertAtで挿入
という流れにしています。
動作例
const items = [
{ id: 1, label: "A" },
{ id: 2, label: "B" },
{ id: 3, label: "C" },
];
const result = insertAfter(
items,
(item) => item.id === 2,
{ id: 99, label: "X" }
);
/*
[
{ id: 1, label: "A" },
{ id: 2, label: "B" },
{ id: 99, label: "X" },
{ id: 3, label: "C" },
]
*/
JavaScript「id=2 の直後に X を挿入する」という、業務でありがちな操作が一行で書けます。
要素挿入ユーティリティで意識してほしいポイント
インデックスの扱いを“仕様として決める”
insertAt では、インデックスをこう扱いました。
- マイナス → 0(先頭)
- 長さより大きい → 末尾
- それ以外 → その位置
これをユーティリティの“仕様”として決めておくと、呼び出し側が「いちいちガードを書く」必要がなくなります。
「範囲外ならエラーにする」のも一つの方針ですが、
業務では「とりあえず先頭か末尾に寄せる」ほうが扱いやすいことが多いです。
非破壊を徹底する
insertAt / insertMany / insertAfter は、すべて元の配列を変更しません。
内部で slice やスプレッドを使って、新しい配列を組み立てています。
「配列をいじるときは、まずコピーしてから」というスタイルをユーティリティで固定しておくと、
「どこで配列が変わったのか分からない」系のバグがかなり減ります。
手を動かして感覚をつかむ
コンソールで、次のコードを実際に打ってみてください。
function insertAt(array, index, value) {
if (!Array.isArray(array)) return [];
const len = array.length;
const safeIndex =
index < 0 ? 0 :
index > len ? len :
index;
const head = array.slice(0, safeIndex);
const tail = array.slice(safeIndex);
return [...head, value, ...tail];
}
const a = [1, 2, 4];
console.log(insertAt(a, 2, 3)); // [1, 2, 3, 4]
console.log(insertAt(a, 0, 0)); // [0, 1, 2, 4]
console.log(insertAt(a, 10, 5)); // [1, 2, 4, 5]
console.log(a); // [1, 2, 4](元は変わらない)
JavaScript「どの位置にどう挿入されるか」「元の配列が変わらないこと」を、自分の目で確認してみてください。
そのうえで、自分のプロジェクトに
export function insertAt(...) { ... }
export function insertMany(...) { ... }
export function insertAfter(...) { ... }
JavaScriptのような関数を置き、「配列に要素を差し込みたくなったら、必ずこの“要素挿入ユーティリティ”を通す」と決めてみてください。
それだけで、splice 直書きの危うさから、一段“安全な配列操作”に進めます。
