何をしたいユーティリティか:「配列比較」
「配列比較」は、「この2つの配列は同じか?」を判定する処理です。
ここでいう「同じか?」は、基本的に次の3つを満たすことを意味します。
- 要素数(長さ)が同じ
- 各インデックスの要素が同じ
- 要素の順番も同じ
つまり、[1, 2, 3] と [1, 2, 3] は「同じ」。[1, 2, 3] と [3, 2, 1] は「違う」。[1, 2] と [1, 2, 3] も「違う」です。
業務では、例えばこういう場面で使います。
- 「前回の検索条件と同じかどうか」を判定したい
- 「選択中のIDリストが変わったかどうか」を知りたい
- 「APIから返ってきた配列が前回と同じなら再描画をスキップしたい」
このとき、「配列同士を比較するユーティリティ」があると、毎回同じロジックを安全に使い回せます。
基本形:プリミティブ配列の比較 equalsArray
まずは「中身がプリミティブ(数値・文字列など)」の配列から
一番よくあるのは、ID やコード、文字列など「プリミティブ値」の配列を比較するケースです。
ここでは「要素数」「順番」「値」がすべて同じかどうかをチェックします。
function equalsArray(a, b) {
if (a === b) {
return true;
}
if (!Array.isArray(a) || !Array.isArray(b)) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
JavaScript重要なポイントをかみ砕きます。
まず a === b をチェックしているのは、「同じ配列インスタンスなら即 true」でいいからです。
次に「どちらも配列でなければ false」。
長さが違えば、その時点で絶対に違うので false。
最後に、各インデックスの要素を !== で比較し、1つでも違えば false。
全部同じなら true。
これで、「プリミティブ配列の完全一致」を判定できます。
動作例
equalsArray([1, 2, 3], [1, 2, 3]); // true
equalsArray([1, 2, 3], [3, 2, 1]); // false
equalsArray([1, 2], [1, 2, 3]); // false
equalsArray(null, [1, 2]); // false
equalsArray([1, 2], [1, "2"]); // false
JavaScript「順番も含めて同じかどうか」を見たいときは、この equalsArray で十分です。
オブジェクト配列を比較したいときの考え方
そのまま === で比べると「同じ参照かどうか」になる
オブジェクト配列を === で比べると、「中身が同じかどうか」ではなく「同じオブジェクトかどうか」になります。
const a = [{ id: 1 }];
const b = [{ id: 1 }];
a[0] === b[0]; // false(別オブジェクト)
JavaScriptなので、「オブジェクト配列の中身が同じかどうか」を見たいときは、
「何をもって“同じ”とみなすか」を決める必要があります。
例えば、次のようなルールが考えられます。
- id が同じなら同じとみなす
- 全プロパティが同じなら同じとみなす
- 特定のキーだけ比較する
ここをユーティリティの引数として渡せるようにすると、業務で使いやすくなります。
比較関数を受け取る equalsArrayBy
function equalsArrayBy(a, b, equalsItem) {
if (a === b) {
return true;
}
if (!Array.isArray(a) || !Array.isArray(b)) {
return false;
}
if (a.length !== b.length) {
return false;
}
if (typeof equalsItem !== "function") {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!equalsItem(a[i], b[i], i)) {
return false;
}
}
return true;
}
JavaScriptここでは、「各要素同士をどう比較するか」を equalsItem に任せています。equalsItem が true を返し続ければ配列は等しい、どこかで false を返したら等しくない、というルールです。
動作例:id が同じなら同じとみなす
const arr1 = [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
];
const arr2 = [
{ id: 1, name: "A (changed)" },
{ id: 2, name: "B" },
];
const sameById = equalsArrayBy(
arr1,
arr2,
(x, y) => x.id === y.id
); // true
const sameByAll = equalsArrayBy(
arr1,
arr2,
(x, y) => x.id === y.id && x.name === y.name
); // false
JavaScript「id だけ見れば同じ配列」とみなすか、「全プロパティまで含めて同じ」とみなすかを、equalsItem の中身で自由に決められます。
業務でよくある「配列比較」のパターン
検索条件が変わったかどうかを判定する
検索条件を配列で持っているケースを考えます。
let prevIds = [];
function shouldSearch(nextIds) {
const same = equalsArray(prevIds, nextIds);
prevIds = nextIds;
return !same;
}
JavaScriptここでは、「前回と同じ ID 配列なら検索しない」「違っていたら検索する」という判定をしています。equalsArray があることで、「配列が変わったかどうか」を一行で書けます。
選択中の ID リストが変わったかどうか
チェックボックスなどで「選択中の ID リスト」を配列で持っているケース。
function hasSelectionChanged(prevSelectedIds, nextSelectedIds) {
return !equalsArray(prevSelectedIds, nextSelectedIds);
}
JavaScriptここでのポイントは、「配列の比較ロジックをコンポーネントや画面にベタ書きしない」ことです。
ユーティリティに閉じ込めておくことで、どこでも同じルールで比較できます。
「順番を無視して比較したい」場合との違い
ここまでの equalsArray / equalsArrayBy は、「順番も含めて同じかどうか」を見ています。
しかし、業務によっては「順番はどうでもよくて、要素の集合が同じなら OK」というケースもあります。
例えば、「選択中のタグ ID の集合が同じなら、順番は気にしない」などです。
その場合は、「順番を無視した比較」用のユーティリティを別で用意するのが安全です。
順番を無視して比較する equalsArrayAsSet(プリミティブ版)
function equalsArrayAsSet(a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) {
return false;
}
if (a.length !== b.length) {
return false;
}
const setA = new Set(a);
const setB = new Set(b);
if (setA.size !== setB.size) {
return false;
}
for (const v of setA) {
if (!setB.has(v)) {
return false;
}
}
return true;
}
JavaScriptこれで、次のような判定ができます。
equalsArrayAsSet([1, 2, 3], [3, 2, 1]); // true
equalsArrayAsSet([1, 2, 2], [1, 2]); // false(重複数が違う)
JavaScript「順番も見る equalsArray」と「順番は無視する equalsArrayAsSet」を分けておくと、
「この比較は何を見ているのか」がとても分かりやすくなります。
配列比較ユーティリティで意識してほしいポイント
何をもって「同じ」とみなすかをはっきりさせる
配列比較は、「何を同一性の基準にするか」がすべてです。
プリミティブ配列なら「値と順番が同じ」。
オブジェクト配列なら「id が同じ」「全プロパティが同じ」「特定のキーだけ同じ」など。
順番を含めるか、無視するか。
これをユーティリティの関数名や引数(equalsArray / equalsArrayBy / equalsArrayAsSet)で表現しておくと、
コードを読む人が迷いません。
非破壊であること
比較は「見るだけ」であり、配列の中身を変える必要は一切ありません。
ユーティリティの中で sort などの破壊的操作をしないように注意してください。
もし順番を無視した比較のためにソートしたくなったら、
必ず slice() でコピーしてからソートするようにします。
「配列じゃないもの」が来ても安全に扱う
equalsArray / equalsArrayBy / equalsArrayAsSet は、すべて最初に Array.isArray をチェックしています。
現実の業務コードでは、「本当は配列のはずだけど、何かの拍子に null やオブジェクトが来る」ことが普通にあります。
そこで例外を投げて落ちるより、「false を返す」と決めておくほうが、呼び出し側のコードがシンプルになります。
手を動かして感覚をつかむ
コンソールで、次のコードを実際に打ってみてください。
function equalsArray(a, b) {
if (a === b) return true;
if (!Array.isArray(a) || !Array.isArray(b)) return false;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
function equalsArrayBy(a, b, equalsItem) {
if (a === b) return true;
if (!Array.isArray(a) || !Array.isArray(b)) return false;
if (a.length !== b.length) return false;
if (typeof equalsItem !== "function") return false;
for (let i = 0; i < a.length; i++) {
if (!equalsItem(a[i], b[i], i)) return false;
}
return true;
}
function equalsArrayAsSet(a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) return false;
if (a.length !== b.length) return false;
const setA = new Set(a);
const setB = new Set(b);
if (setA.size !== setB.size) return false;
for (const v of setA) {
if (!setB.has(v)) return false;
}
return true;
}
equalsArray([1, 2, 3], [1, 2, 3]);
equalsArray([1, 2, 3], [3, 2, 1]);
const arr1 = [{ id: 1 }, { id: 2 }];
const arr2 = [{ id: 1 }, { id: 2 }];
equalsArrayBy(arr1, arr2, (x, y) => x.id === y.id);
equalsArrayAsSet([1, 2, 3], [3, 2, 1]);
JavaScript「どの比較が true になって、どれが false になるか」を自分の目で確かめてみてください。
そのうえで、自分のプロジェクトに
export function equalsArray(...) { ... }
export function equalsArrayBy(...) { ... }
export function equalsArrayAsSet(...) { ... }
JavaScriptのような関数を置き、「配列が“同じかどうか”を判定したくなったら、必ずこの“配列比較ユーティリティ”を通す」と決めてみてください。
それだけで、「なんとなく比較しているコード」から、「意図がはっきりした比較ロジック」に一段レベルアップできます。
