JavaScript Tips | 配列ユーティリティ:インデックス検索

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「インデックス検索」

「インデックス検索」は、配列の中から「条件に合う要素がどこにあるか」を調べて、その位置(インデックス)を返す処理です。
「この値は何番目にある?」「id が一致する要素はどこ?」「最初に条件を満たす要素の位置は?」といった問いに答えるためのユーティリティです。

業務だと、例えばこういう場面で使います。

特定のユーザーを配列から探して、その位置に対して削除・置換・挿入をしたい。
選択中リストの中で、ある ID が何番目かを知りたい。
「最初にエラーになった行」のインデックスを取りたい。

ここで大事なのは、「見つからなかったときどうするか」と「何を基準に探すか」を、ユーティリティとして決めておくことです。


基本形1:値で探す indexOf をラップする findIndexOf

プリミティブ値を探す一番シンプルな形

まずは、「この値が配列のどこにあるか」を調べる基本形です。

function findIndexOf(array, value) {
  if (!Array.isArray(array)) {
    return -1;
  }
  return array.indexOf(value);
}
JavaScript

ここでのポイントは、

配列じゃなければ「見つからない」を表す -1 を返す。
indexOf は「最初に一致した位置」を返し、見つからなければ -1 を返す。

というシンプルなルールです。

動作例

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

findIndexOf(arr, "a"); // 0
findIndexOf(arr, "b"); // 1(最初の b)
findIndexOf(arr, "x"); // -1
findIndexOf(null, "a"); // -1
JavaScript

「値がどこにあるか」を知りたいだけなら、このレベルで十分です。


基本形2:条件で探す findIndex(業務で一番使う形)

predicate(条件関数)で「探す基準」を決める

業務では、「id が一致する要素」「フラグが true の要素」「スコアが一定以上の最初の要素」など、
単純な値一致ではなく「条件」で探したいことが多いです。

そのときに使うのが Array.prototype.findIndex で、それをユーティリティとしてラップした形がこちらです。

function findIndexBy(array, predicate) {
  if (!Array.isArray(array)) {
    return -1;
  }
  if (typeof predicate !== "function") {
    return -1;
  }

  return array.findIndex(predicate);
}
JavaScript

ここでは、

配列じゃなければ -1
predicate が関数でなければ -1
それ以外は findIndex にそのまま渡す。

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

動作例:id が一致する要素のインデックスを探す

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

const index = findIndexBy(users, (u) => u.id === 2);
// index = 1

const notFound = findIndexBy(users, (u) => u.id === 99);
// notFound = -1
JavaScript

「id=2 のユーザーは何番目か?」という問いに、findIndexBy で答えられます。


インデックス検索を他のユーティリティと組み合わせる

見つけた位置に対して削除・置換・挿入をする

インデックス検索は、それ単体で使うよりも「次の操作のための下準備」として使うことが多いです。

例えば、「id が一致する要素を削除する」ならこう書けます。

function removeById(array, id) {
  const index = findIndexBy(array, (item) => item.id === id);
  if (index === -1) {
    return Array.isArray(array) ? array.slice() : [];
  }
  return removeAt(array, index); // 以前作った removeAt を使う
}
JavaScript

「id が一致する要素を更新する」ならこうです。

function updateById(array, id, updater) {
  const index = findIndexBy(array, (item) => item.id === id);
  if (index === -1 || typeof updater !== "function") {
    return Array.isArray(array) ? array.slice() : [];
  }

  const target = array[index];
  const updated = updater(target);

  return replaceAt(array, index, updated); // 以前作った replaceAt を使う
}
JavaScript

ここでの重要ポイントは、

インデックス検索は「どこを触るか」を決めるための道具。
実際の削除・置換は、別のユーティリティ(removeAt / replaceAt)に任せる。

という役割分担です。


「見つからなかったとき」の扱いを決めておく

-1 をそのまま返すか、例外にするか、何もしないか

indexOffindIndex は、見つからないときに -1 を返します。
ユーティリティでも基本はそれに乗っかりますが、「その後どう扱うか」を決めておくことが大事です。

例えば、

削除系なら「-1 のときは何もしないでコピーだけ返す」。
更新系なら「-1 のときは元の配列をそのまま返す」。
挿入系なら「-1 のときは末尾に挿入する」など。

インデックス検索ユーティリティ自体は「-1 を返すだけ」にしておき、
呼び出し側(削除・更新ユーティリティ)で「-1 のときの振る舞い」を決める、という設計が読みやすくなります。


業務でありがちなパターンをユーティリティにしておく

「存在チェック」と「インデックス取得」を分ける

よくあるのが、「その要素が存在するかどうか」と「どこにあるか」を両方知りたいパターンです。

function hasBy(array, predicate) {
  return findIndexBy(array, predicate) !== -1;
}
JavaScript

こうしておくと、

「存在するかどうかだけ知りたい」 → hasBy
「どこにあるかも知りたい」 → findIndexBy

と使い分けられます。

「なければ末尾に挿入する」ようなパターン

例えば、「id が一致する要素があれば更新、なければ末尾に追加」という業務ロジック。

function upsertById(array, item) {
  if (!Array.isArray(array) || !item || typeof item.id === "undefined") {
    return Array.isArray(array) ? array.slice() : [];
  }

  const index = findIndexBy(array, (x) => x.id === item.id);
  if (index === -1) {
    return [...array, item]; // 末尾に追加
  }

  return replaceAt(array, index, item); // 既存を置換
}
JavaScript

ここでも、「インデックス検索」が「更新か追加かを決めるための判断材料」として使われています。


手を動かしてインデックス検索の感覚をつかむ

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

function findIndexOf(array, value) {
  if (!Array.isArray(array)) return -1;
  return array.indexOf(value);
}

function findIndexBy(array, predicate) {
  if (!Array.isArray(array)) return -1;
  if (typeof predicate !== "function") return -1;
  return array.findIndex(predicate);
}

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

console.log(findIndexOf(arr, "b")); // 1
console.log(findIndexOf(arr, "x")); // -1

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

console.log(findIndexBy(users, (u) => u.id === 2));  // 1
console.log(findIndexBy(users, (u) => u.name === "Z")); // -1
JavaScript

「どの条件で何番目が返ってくるか」「見つからないときはどうなるか」を、自分の目で確認してみてください。


まとめ:インデックス検索は“次の操作のための座標を取る”道具

インデックス検索ユーティリティは、それ単体で完結するというより、

削除ユーティリティに「どこを消すか」を教える。
置換ユーティリティに「どこを更新するか」を教える。
挿入ユーティリティに「どこに差し込むか」を教える。

という、「座標を取るための道具」です。

プロジェクトに

export function findIndexOf(...) { ... }
export function findIndexBy(...) { ... }
export function hasBy(...) { ... }
JavaScript

のような関数を置き、「配列の“どこ”を触るか決めたいときは必ずこれを通す」と決めておくと、
配列操作のロジックがきれいに分離されて、読みやすさもバグ耐性も一気に上がります。

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