何をしたいユーティリティか:「条件削除」
「条件削除」は、配列の中から「ある条件に当てはまる要素だけ」を取り除く処理です。
英語だとイメージ的には「removeIf」「reject」「filter-out」みたいな感じですね。
業務だと、例えばこういう場面で使います。
特定のステータスのレコードだけを一覧から消したい。
期限切れのデータだけを削除したい。
フラグが立っているものだけを除外して、残りを処理したい。
毎回 filter を直接書いてもいいんですが、「削除したい条件」をユーティリティに閉じ込めておくと、
コードの意図がはっきりして、バグも減ります。
基本形:条件を関数で受け取る removeBy
「削除したい条件」を関数にする
条件削除の一番素直な形は、「削除したいかどうかを判定する関数」を受け取るユーティリティです。
function removeBy(array, shouldRemove) {
if (!Array.isArray(array)) {
return [];
}
if (typeof shouldRemove !== "function") {
return array.slice();
}
return array.filter((item, index) => !shouldRemove(item, index));
}
JavaScriptここでの重要ポイントをかみ砕きます。
配列でなければ空配列を返す(安全側)。shouldRemove が関数でなければ、そのままコピーして返す(何もしない)。filter の中で「削除したいものは除外する」=!shouldRemove(...) なものだけ残す。
つまり、「shouldRemove が true を返した要素は削除される」「false を返した要素は残る」という動きになります。
動作イメージ
例えば、「値が 0 以下のものを削除したい」場合。
const nums = [10, -5, 0, 20];
const positiveOnly = removeBy(nums, (n) => n <= 0);
// [10, 20]
JavaScript「削除したい条件」を n <= 0 として書き、それを removeBy に渡しています。filter を直接書くのとやっていることは同じですが、「削除」という意図が関数名に出ているのがポイントです。
よくある業務パターン別の条件削除
ステータスで削除する
例えば、次のような配列があるとします。
const items = [
{ id: 1, status: "active" },
{ id: 2, status: "deleted" },
{ id: 3, status: "active" },
];
JavaScript「status: "deleted" のものだけ削除したい」場合。
const visibleItems = removeBy(items, (item) => item.status === "deleted");
/*
[
{ id: 1, status: "active" },
{ id: 3, status: "active" },
]
*/
JavaScriptここでのポイントは、「削除したい条件」を item.status === "deleted" として、
それを removeBy に渡していることです。
期限切れデータを削除する
期限付きのデータを扱う配列を考えます。
const now = new Date("2024-06-01T00:00:00Z");
const items = [
{ id: 1, expiresAt: new Date("2024-05-01T00:00:00Z") },
{ id: 2, expiresAt: new Date("2024-07-01T00:00:00Z") },
];
JavaScript「期限切れ(expiresAt < now)のものを削除したい」場合。
const validItems = removeBy(items, (item) => item.expiresAt < now);
/*
[
{ id: 2, expiresAt: ... },
]
*/
JavaScript「削除したい条件」を item.expiresAt < now として書き、
それを removeBy に渡すだけで、「期限切れ削除」が実現できます。
「削除条件」と「残す条件」を意識して書き分ける
filter との違いを整理する
Array.prototype.filter は、「残したい条件」を書く関数です。
const positiveOnly = nums.filter((n) => n > 0);
JavaScript一方、removeBy は「削除したい条件」を書く関数です。
const positiveOnly = removeBy(nums, (n) => n <= 0);
JavaScriptどちらも結果は同じですが、「頭の中で何を考えるか」が違います。
残したい条件で考えるほうが楽なときは filter を直接使えばいいし、
「削除したい条件」が業務仕様としてはっきりしているときは removeBy のほうが読みやすくなります。
「削除条件を名前付き関数にする」メリット
例えば、「削除条件」が複雑になってきたとき、
それを名前付き関数にしておくと、コードの意図が一気に読みやすくなります。
function shouldRemoveDeleted(item) {
return item.status === "deleted";
}
const visibleItems = removeBy(items, shouldRemoveDeleted);
JavaScript「この配列は“削除済みを除いたもの”なんだな」と、関数名だけで伝わります。
業務ロジックが増えてくるほど、この「名前で伝える」力が効いてきます。
破壊的な条件削除:元の配列を直接いじる版
非破壊版と破壊版の違い
ここまでの removeBy は、「新しい配列を返す非破壊的な関数」でした。
元の配列はそのまま残ります。
一方で、「この配列はここでしか使っていないから、直接中身を削ってしまいたい」という場面もあります。
そのときは、破壊的な条件削除ユーティリティを用意してもいいです。
破壊的 removeByInPlace
function removeByInPlace(array, shouldRemove) {
if (!Array.isArray(array) || typeof shouldRemove !== "function") {
return array;
}
let write = 0;
for (let read = 0; read < array.length; read++) {
const item = array[read];
if (!shouldRemove(item, read)) {
array[write++] = item;
}
}
array.length = write;
return array;
}
JavaScriptここでの重要ポイントは、「2 つのインデックスを使って上書きしている」ことです。
read で元の配列を順に読み、
削除したくないものだけを write の位置に詰めていく。
最後に array.length = write で、余った要素を切り捨てる。
これで、「元の配列を直接削る」ことができます。
動作例
const nums = [10, -5, 0, 20];
removeByInPlace(nums, (n) => n <= 0);
// nums は [10, 20] に変わる
JavaScript破壊的なので、「ここで削ると、以降ずっと削られた状態になる」ことを意識して使う必要があります。
ログバッファや一時配列など、「1 箇所でだけ管理している配列」に対して使うのが向いています。
条件削除ユーティリティで意識してほしいポイント
条件の意味を関数名で伝える
削除条件が業務仕様そのものなので、
それを「名前付き関数」にしておくと、コードの意図がとても読みやすくなります。
shouldRemoveDeletedshouldRemoveExpiredshouldRemoveInactive
こういう名前を付けておくと、「何を削っているのか」が一目で分かります。item.status === "deleted" があちこちに散らばるより、ずっとメンテしやすいです。
非破壊版を基本にして、破壊版は慎重に使う
基本は removeBy のような「新しい配列を返す非破壊的な関数」を使うほうが安全です。
元の配列をいじらないので、「どこで削られたのか」が追いやすいからです。
どうしてもパフォーマンスやメモリの都合で破壊的にしたいときだけ、removeByInPlace のような関数を使う、というスタンスがおすすめです。
filter と removeBy を使い分ける
「残したい条件」で考えるほうが自然なら、素直に filter を使えばいいです。
「削除したい条件」が仕様としてはっきりしているなら、removeBy のほうが読みやすくなります。
どちらも中身は同じ filter ですが、
「頭の中で何を考えているか」をコードに反映させることで、
未来の自分やチームメイトが理解しやすいコードになります。
手を動かして感覚をつかむ
コンソールで、次のようなコードを実際に打ってみてください。
function removeBy(array, shouldRemove) {
if (!Array.isArray(array)) {
return [];
}
if (typeof shouldRemove !== "function") {
return array.slice();
}
return array.filter((item, index) => !shouldRemove(item, index));
}
const nums = [10, -5, 0, 20];
const positiveOnly = removeBy(nums, (n) => n <= 0);
const items = [
{ id: 1, status: "active" },
{ id: 2, status: "deleted" },
{ id: 3, status: "active" },
];
const visibleItems = removeBy(items, (item) => item.status === "deleted");
JavaScript「どの要素が削除されて、どの要素が残るか」「削除条件を変えると結果がどう変わるか」を、自分の目で確かめてみてください。
そのうえで、自分のプロジェクトに
export function removeBy(...) { ... }
// 必要なら
export function removeByInPlace(...) { ... }
JavaScriptを置き、「配列から“条件に合うものだけ”を削除したくなったら、必ずこの“条件削除ユーティリティ”を通す」と決めてみてください。
それだけで、バラバラに書かれた filter 条件が整理されて、業務ロジックの見通しがぐっと良くなります。
