JavaScript Tips | 配列ユーティリティ:要素置換

JavaScript JavaScript
スポンサーリンク

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

「要素置換」は、配列の中の特定の要素を“別の値に差し替える”処理です。
「インデックス 2 の要素を新しい値にしたい」「id が一致する要素だけ更新したい」など、業務でめちゃくちゃよく使います。

ここで大事なのは、

  • 元の配列を壊すか(破壊的)、壊さないか(非破壊的)
  • 何を基準に「置き換える対象」を決めるか(インデックス・値・条件)

この2つを、ユーティリティとして“決めておく”ことです。


基本形:インデックス指定で置換する replaceAt(非破壊)

元の配列を壊さない「安全な置換」

まずは、インデックスで指定して、元の配列を変更せずに新しい配列を返す形からいきます。

function replaceAt(array, index, value) {
  if (!Array.isArray(array)) {
    return [];
  }

  const len = array.length;
  if (index < 0 || index >= len) {
    return array.slice();
  }

  const head = array.slice(0, index);
  const tail = array.slice(index + 1);

  return [...head, value, ...tail];
}
JavaScript

ここでのポイントは、次の通りです。
配列でなければ空配列を返す。
インデックスが範囲外なら何もせずコピーだけ返す。
slice(0, index) で「置換対象より前」、slice(index + 1) で「置換対象より後ろ」を取り出し、
真ん中に新しい value を挟んで新しい配列を作る。

元の配列は一切変更されません。

動作例

const arr = [10, 20, 30];

const r1 = replaceAt(arr, 1, 99);
// [10, 99, 30]

const r2 = replaceAt(arr, 0, 5);
// [5, 20, 30]

const r3 = replaceAt(arr, 10, 999);
// [10, 20, 30](範囲外なのでそのまま)

console.log(arr); // [10, 20, 30](元は変わらない)
JavaScript

値で置換する replaceValue / replaceAll

最初に見つかった1件だけ置換する replaceValue

「この値に一致する最初の1件だけ、新しい値に差し替えたい」パターンです。

function replaceValue(array, oldValue, newValue) {
  if (!Array.isArray(array)) {
    return [];
  }

  const index = array.indexOf(oldValue);
  if (index === -1) {
    return array.slice();
  }

  return replaceAt(array, index, newValue);
}
JavaScript

indexOf で最初に見つかった位置を取り、見つからなければコピーだけ返します。
見つかったら replaceAt を使って、その位置の要素を新しい値に置き換えた配列を返します。

動作イメージはこうです。

const arr = ["a", "b", "c", "b"];

const r1 = replaceValue(arr, "b", "X");
// ["a", "X", "c", "b"](最初の b だけ置換)

const r2 = replaceValue(arr, "z", "X");
// ["a", "b", "c", "b"](見つからないのでそのまま)
JavaScript

一致するものを全部置換する replaceAll

「この値に一致する要素を全部、新しい値に差し替えたい」パターンです。

function replaceAll(array, oldValue, newValue) {
  if (!Array.isArray(array)) {
    return [];
  }

  return array.map((item) => (item === oldValue ? newValue : item));
}
JavaScript

map は「各要素を変換して新しい配列を作る」ので、
item === oldValue のときだけ newValue に差し替え、それ以外はそのまま返しています。

動作例:

const arr = ["a", "b", "c", "b"];

const r = replaceAll(arr, "b", "X");
// ["a", "X", "c", "X"]

console.log(arr); // ["a", "b", "c", "b"](元は変わらない)
JavaScript

条件で置換する replaceIf(業務で一番使いやすい形)

predicate(条件関数)で「置換対象」を決める

業務では、「id が一致する要素だけ更新したい」「フラグが true のものだけ値を変えたい」など、
単純な値一致ではなく「条件」で置換したいことが多いです。

function replaceIf(array, predicate, replacer) {
  if (!Array.isArray(array)) {
    return [];
  }
  if (typeof predicate !== "function" || typeof replacer !== "function") {
    return array.slice();
  }

  return array.map((item, index) =>
    predicate(item, index) ? replacer(item, index) : item
  );
}
JavaScript

ここでは、

  • predicate(item, index) が true なら、その要素を置換対象とみなす
  • replacer(item, index) が返した値で置き換える
  • false なら元の要素をそのまま残す

というルールにしています。

動作例:id が一致する要素だけ更新

const users = [
  { id: 1, name: "A", active: false },
  { id: 2, name: "B", active: false },
  { id: 3, name: "C", active: false },
];

const result = replaceIf(
  users,
  (u) => u.id === 2,
  (u) => ({ ...u, active: true })
);

/*
[
  { id: 1, name: "A", active: false },
  { id: 2, name: "B", active: true },
  { id: 3, name: "C", active: false },
]
*/
JavaScript

ここでのポイントは、「置き換えるときにオブジェクトをスプレッドでコピーしている」ことです。
({ ...u, active: true }) とすることで、「元のオブジェクトを壊さずに、新しいオブジェクトとして更新」できます。


破壊的な直接代入とどう付き合うか

直接代入の問題点

JavaScript では、インデックスを指定して直接代入できます。

const arr = [10, 20, 30];
arr[1] = 99;

console.log(arr); // [10, 99, 30]
JavaScript

これは「元の配列をその場で書き換える」操作です。
便利ですが、業務コードで多用すると、

  • 「どこで配列が変わったのか分からない」
  • 「別の処理が想定外の値で動き始める」

といったバグにつながりやすくなります。

ユーティリティで「非破壊」を標準にする

replaceAt / replaceValue / replaceAll / replaceIf は、すべて元の配列を変更しません。
内部で slicemap を使って、新しい配列を組み立てています。

「配列の要素を更新したいときは、まず“置換ユーティリティ”を通す」と決めておくと、
配列の変更箇所が明確になり、バグがぐっと減ります。


手を動かして感覚をつかむ

コンソールで、次のコードを実際に打ってみてください。

function replaceAt(array, index, value) {
  if (!Array.isArray(array)) return [];
  const len = array.length;
  if (index < 0 || index >= len) return array.slice();
  const head = array.slice(0, index);
  const tail = array.slice(index + 1);
  return [...head, value, ...tail];
}

function replaceValue(array, oldValue, newValue) {
  if (!Array.isArray(array)) return [];
  const index = array.indexOf(oldValue);
  if (index === -1) return array.slice();
  return replaceAt(array, index, newValue);
}

function replaceAll(array, oldValue, newValue) {
  if (!Array.isArray(array)) return [];
  return array.map((item) => (item === oldValue ? newValue : item));
}

function replaceIf(array, predicate, replacer) {
  if (!Array.isArray(array)) return [];
  if (typeof predicate !== "function" || typeof replacer !== "function") {
    return array.slice();
  }
  return array.map((item, index) =>
    predicate(item, index) ? replacer(item, index) : item
  );
}

const arr = [10, 20, 30, 20];

console.log(replaceAt(arr, 1, 99));              // [10, 99, 30, 20]
console.log(replaceValue(arr, 20, 50));          // [10, 50, 30, 20]
console.log(replaceAll(arr, 20, 50));            // [10, 50, 30, 50]
console.log(
  replaceIf(arr, (v) => v >= 30, (v) => v * 10)  // [10, 20, 300, 20]
);
console.log(arr);                                // [10, 20, 30, 20]
JavaScript

「どの置換がどう効いているか」「元の配列が変わっていないこと」を、自分の目で確認してみてください。


まとめ:要素置換ユーティリティを“更新の標準手段”にする

業務コードでは、「配列の中の一部だけ更新したい」場面が本当に多いです。
そのたびにインデックス代入や map を直書きするのではなく、

export function replaceAt(...) { ... }
export function replaceValue(...) { ... }
export function replaceAll(...) { ... }
export function replaceIf(...) { ... }
JavaScript

のようなユーティリティを用意して、「配列の要素を更新したいときは必ずこれを通す」と決めておくと、
更新ロジックが統一されて、読みやすさもバグ耐性も一気に上がります。

「何を基準に置換するか」と「元配列を壊すかどうか」を、
毎回意識的に選べるようになることが、配列操作を“業務レベル”に引き上げるポイントです。

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