JavaScript Tips | 配列ユーティリティ:要素挿入

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「要素挿入」

「要素挿入」は、配列の好きな位置に新しい要素を差し込む処理です。
[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]
JavaScript

index = 0 のときは、「全部の前に挿入」です。

末尾に挿入する

const arr = [1, 2];
const result = insertAt(arr, 5, 3);
// [1, 2, 3]
JavaScript

index = 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 直書きの危うさから、一段“安全な配列操作”に進めます。

タイトルとURLをコピーしました