テーマの整理:「配列変更監視」とは何か
「配列変更監視」は、配列の中身が変わった瞬間を検知して、何かの処理(再計算・再描画・ログなど)を自動で走らせるための仕組みです。 業務だと、例えば次のような場面で欲しくなります。
画面に表示している一覧が更新されたら、自動で再描画したい。 配列が変わったタイミングで、集計結果を再計算したい。 ログや監査のために「いつ・どう変わったか」を記録したい。
ここでは、初心者でも扱いやすい「配列変更監視」の考え方とユーティリティを、例題付きでかみ砕いて説明します。
基本コンセプト 1:配列を「ラップして」変更をフックする
なぜ「ラップ」が必要なのか
JavaScript の配列は、そのままだと「いつ変更されたか」を自動で教えてくれません。 push や splice をしても、誰かが監視してくれるわけではないので、 自分で「変更されたこと」を検知する仕組みを作る必要があります。
そこで出てくるのが、 「配列をラップして、変更系メソッドを通るたびにコールバックを呼ぶ」という発想です。
シンプルなラッパー:createObservableArray
まずは、初心者向けに分かりやすい「ラッパー関数」を作ってみます。
function createObservableArray(initial = [], onChange) {
const arr = Array.isArray(initial) ? initial.slice() : [];
function notify() {
if (typeof onChange === "function") {
onChange(arr);
}
}
return {
get value() {
return arr;
},
push(...items) {
arr.push(...items);
notify();
},
splice(...args) {
arr.splice(...args);
notify();
},
set(index, value) {
arr[index] = value;
notify();
},
replace(newArray) {
arr.length = 0;
arr.push(...newArray);
notify();
},
};
}
JavaScriptここでの重要ポイントをかみ砕きます。
内部に「本体の配列 arr」を持つ。 onChange は「配列が変わったときに呼ばれるコールバック」。 push や splice などの変更メソッドのあとに notify() を呼ぶことで、「変更されたこと」を外に伝える。 value で「現在の配列」を参照できる。
つまり、「配列を直接触るのではなく、このラッパーを通して操作することで、変更を監視できる」仕組みです。
例題 1:配列が変わるたびにログを出す
シナリオ
ユーザー一覧を配列で持っていて、 追加・削除・更新があるたびに「今の一覧」をログに出したい。
コード
const users = createObservableArray([], (arr) => {
console.log("users changed:", arr);
});
users.push({ id: 1, name: "Alice" });
users.push({ id: 2, name: "Bob" });
users.set(1, { id: 2, name: "Bob Jr." });
users.splice(0, 1);
JavaScriptここで起きていることを言葉で追うと、
push でユーザーを追加すると、内部配列が変わり onChange が呼ばれる。 set でユーザーを更新すると、同じく onChange が呼ばれる。 splice で削除すると、また onChange が呼ばれる。
結果として、「配列が変わるたびにログが出る」状態が簡単に作れます。
基本コンセプト 2:Proxy を使った「より自動的な監視」
Proxy とは何か
Proxy は、オブジェクトや配列への操作を“横取り”して、好きな処理を挟める仕組みです。 これを使うと、「配列のメソッドをラップする」のではなく、 「配列そのものへの操作を監視する」ことができます。
Proxy 版の配列変更監視
初心者向けに、最低限の Proxy を使った例を見てみます。
function observeArray(array, onChange) {
if (!Array.isArray(array)) {
array = [];
}
return new Proxy(array, {
set(target, prop, value) {
const result = Reflect.set(target, prop, value);
if (prop !== "length" && typeof onChange === "function") {
onChange(target);
}
return result;
},
});
}
JavaScriptここでのポイントは、
set トラップで「配列の要素やプロパティが書き換えられた瞬間」を捕まえる。 length の変更(内部的なサイズ変更)は無視して、要素の変更だけで onChange を呼ぶ。 戻り値は「監視付きの配列」で、普通の配列と同じように使える。
という構造になっていることです。
例題 2:Proxy で配列変更を監視する
コード
const raw = [];
const observed = observeArray(raw, (arr) => {
console.log("changed:", arr);
});
observed.push(1);
observed.push(2);
observed[0] = 10;
observed.splice(1, 1);
JavaScriptここでは、
observed は「監視付きの配列」。 push や splice、observed[0] = 10 のような代入が行われるたびに、set トラップが動いて onChange が呼ばれる。
ラッパー版と違って、「配列っぽさ」をほぼそのまま保ったまま監視できるのが Proxy の強みです。
配列変更監視の「実務的な使いどころ」
再計算・再描画のトリガーとして使う
業務コードでは、「配列が変わったら何かを再計算したい」という場面が多いです。
例えば、
一覧データが変わったら、合計値や統計を再計算する。 表示用のソート済み配列を作り直す。 グラフのデータを更新する。
こういうときに、配列変更監視の onChange で「再計算関数」を呼ぶようにしておけば、 「配列をいじるだけで、必要な処理が自動で走る」状態を作れます。
監査・ログのために「変更履歴」を残す
もう一つの実務的な使いどころは、「変更履歴の記録」です。
例えば、
いつ、誰が、どの要素を追加・削除・更新したか。 どの画面操作で、配列がどう変わったか。
こういう情報を onChange の中で記録しておけば、 後から「何が起きたか」を追いやすくなります。
初心者向けには、まずは「変更のたびに console.log する」ところから始めて、 慣れてきたらファイルや DB に書き出すイメージを持つとよいです。
配列変更監視で意識してほしい重要ポイント
「どこからどこまでを監視するか」を決める
配列変更監視は、便利だからといって何でもかんでも付けると、 ログがうるさくなったり、パフォーマンスに影響したりします。
そこで、
この配列は、変更がビジネス的に意味を持つか? 変更のたびに何かを再計算・再描画したいか? 監査やログの対象にしたいか?
といった観点で、「監視する配列」を絞ることが大事です。
「監視付き配列」をちゃんと通す
ラッパー版でも Proxy 版でも、 「監視付きの配列」を返しているのに、 うっかり元の生配列を直接いじってしまうと、監視が効きません。
例えば、
const users = createObservableArray(...); で作ったのに、 users.value ではなく内部の配列を直接触ってしまう。
こういうミスを避けるために、
監視付き配列は変数名で分かりやすくする(observableUsers など)。 生配列を外に出さない(ラッパーの中だけに閉じ込める)。
といった工夫をすると安全です。
手を動かして「配列変更監視」の感覚をつかむ
次のコードを実行して、ラッパー版と Proxy 版の違いを体感してみてください。
function createObservableArray(initial = [], onChange) {
const arr = Array.isArray(initial) ? initial.slice() : [];
function notify() {
if (typeof onChange === "function") {
onChange(arr);
}
}
return {
get value() {
return arr;
},
push(...items) {
arr.push(...items);
notify();
},
splice(...args) {
arr.splice(...args);
notify();
},
set(index, value) {
arr[index] = value;
notify();
},
};
}
function observeArray(array, onChange) {
if (!Array.isArray(array)) {
array = [];
}
return new Proxy(array, {
set(target, prop, value) {
const result = Reflect.set(target, prop, value);
if (prop !== "length" && typeof onChange === "function") {
onChange(target);
}
return result;
},
});
}
console.log("=== wrapper version ===");
const usersWrapped = createObservableArray([], (arr) => {
console.log("wrapped changed:", arr);
});
usersWrapped.push({ id: 1 });
usersWrapped.set(0, { id: 1, name: "Alice" });
console.log("=== proxy version ===");
const raw = [];
const usersProxy = observeArray(raw, (arr) => {
console.log("proxy changed:", arr);
});
usersProxy.push({ id: 2 });
usersProxy[0] = { id: 2, name: "Bob" };
JavaScriptログを見れば、
ラッパー版は「メソッド経由」で変更を検知している。 Proxy 版は「配列への代入も含めて」変更を検知している。
という違いがはっきり分かるはずです。
まとめ:配列変更監視ユーティリティで「変化に反応するコード」を育てる
「配列変更監視」は、 「配列が変わった瞬間に、何かを自動でやりたい」という欲求をコードに落としたものです。
プロジェクトに例えば次のような形で置いておくイメージです。
export function createObservableArray(initial, onChange) { ... }
export function observeArray(array, onChange) { ... }
JavaScriptそして、
ビジネス的に意味のある配列には、必ずどちらかの監視を付ける。 変更のたびに再計算・再描画・ログなどをトリガーする。
こう決めておくと、 「配列が変わったのに何も更新されない」「いつ変わったか分からない」といったバグが減り、 コードが「変化にちゃんと反応する」ようになっていきます。
配列変更監視は、ただのテクニックではなく、 「状態の変化をちゃんと扱う」ための考え方そのものです。 そこを意識してユーティリティを育てていくと、業務コードの質が一段上がります。
