JavaScript | 配列・オブジェクト:実務パターン – フィルタ条件切替

JavaScript JavaScript
スポンサーリンク

フィルタ条件切替とは何か

フィルタ条件切替は「現在の条件(カテゴリ、価格帯、検索語、ステータスなど)をON/OFFや値変更で切り替え、その都度配列を絞り込む」実務パターンです。ここが重要です:条件を“関数(述語=predicate)”として組み立てて合成(AND/OR)できるようにしておくと、切替がシンプルで拡張に強くなります。配列は filter による非破壊処理が基本です。


単純な条件切替(ON/OFF・値変更の基本形)

真偽フラグと値を同時に扱う

const items = [
  { category: "book", price: 1200, active: true },
  { category: "pen",  price: 200,  active: false },
  { category: "book", price: 800,  active: true }
];

// フィルタ状態(UIの設定を想定)
const filters = {
  enabledOnly: true,        // ONなら active:true のみ
  category: "book",         // 空やnullなら無視
  minPrice: 500             // undefinedなら無視
};

// 条件を適用
function applyFilters(list, f) {
  return list.filter(it =>
    (!f.enabledOnly || it.active) &&                                 // ONなら適用、OFFなら常に通す
    (!f.category || it.category === f.category) &&                   // 値があれば一致を要求
    (f.minPrice == null || it.price >= f.minPrice)                   // null/undefinedなら無視
  );
}

const result = applyFilters(items, filters);
JavaScript

ここが重要です:ON/OFFは「ONなら条件を適用、OFFなら必ず通す」と書くと切替が直感的になります。値系は == null(null/undefined)で“欠損なら無視”と判定するのが実務に便利です。


述語関数(predicate)を合成する設計

条件を小さな関数に分けてANDで合成

const whereEnabled   = f => it => (!f.enabledOnly || it.active);
const whereCategory  = f => it => (!f.category || it.category === f.category);
const whereMinPrice  = f => it => (f.minPrice == null || it.price >= f.minPrice);

function and(...preds) {
  return x => preds.every(p => p(x));
}

function applyFilters(list, filters) {
  const pred = and(
    whereEnabled(filters),
    whereCategory(filters),
    whereMinPrice(filters)
  );
  return list.filter(pred);
}
JavaScript

ここが重要です:述語を小分けにすると追加・削除・差し替えが容易になります。合成は every(AND)で決定的・読みやすい。ORが欲しい条件は別途 or(p1, p2) を用意して使い分けます。

AND/OR の切替(柔軟なクエリ)

function or(...preds) {
  return x => preds.some(p => p(x));
}

// 例:カテゴリが "book" または "pen" のどちらか
const pred = or(
  it => it.category === "book",
  it => it.category === "pen"
);
const filtered = items.filter(pred);
JavaScript

ここが重要です:UIで“複数選択”を許すと OR が必要になります。ANDとORの境界を仕様で明確化し、述語の合成で表現します。


検索語・正規化・部分一致(実務でよくあるパターン)

入力の正規化を先に行う

const normalize = s =>
  s.trim().toLowerCase().replace(/[A-Za-z]/g, ch =>
    String.fromCharCode(ch.charCodeAt(0) - 0xFEE0)
  );

function whereQuery(f) {
  if (!f.q) return () => true; // 無視
  const q = normalize(f.q);
  return it => normalize(it.name).includes(q);
}

// 例
const filters = { q: "  BOOK " };
const pred = whereQuery(filters);
const result = items.filter(pred);
JavaScript

ここが重要です:表記ゆれ(全角半角・大文字小文字・前後空白)を正規化してから比較することで、期待どおりの絞り込みになります。正規化は“キー関数”に閉じ込めて再利用できる形にします。


範囲・並び・多段条件の同時適用

数値範囲(min/max)とソートの組み合わせ

function whereRange(f) {
  return it =>
    (f.min == null || it.price >= f.min) &&
    (f.max == null || it.price <= f.max);
}

function apply(list, f) {
  const pred = and(whereRange(f), whereQuery(f));
  const filtered = list.filter(pred);
  return f.sort
    ? filtered.toSorted((a, b) =>
        f.sort === "priceAsc"  ? a.price - b.price :
        f.sort === "priceDesc" ? b.price - a.price :
        a.name.localeCompare(b.name, "ja"))
    : filtered;
}
JavaScript

ここが重要です:フィルタと並び替えは“別レイヤー”。まず絞り、次にソート。ソートは toSorted の非破壊版を使い、元配列を壊さない。


状態管理(フィルタの更新と適用の分離)

状態を更新する関数と、適用する関数を分ける

function updateFilters(prev, patch) {
  return { ...prev, ...patch };
}

function getFiltered(list, filters) {
  return applyFilters(list, filters);
}

// 使い方(UIで切替)
let filters = { enabledOnly: false, category: null, minPrice: null };
filters = updateFilters(filters, { enabledOnly: true });
const view = getFiltered(items, filters);
JavaScript

ここが重要です:更新(何をON/OFF・値変更するか)と適用(配列へ絞り込み)を分離すると、テスト・再利用が簡単になり、UIロジックが明快になります。更新はイミュータブルで。


パフォーマンス最適化(無駄な再計算を減らす)

キーの前計算(Schwartzian transform)

// 例:重い正規化を何度も計算しない
const prepared = items.map(x => ({ x, key: normalize(x.name) }));
const q = normalize("book");
const filtered = prepared
  .filter(p => p.key.includes(q))
  .map(p => p.x);
JavaScript

ここが重要です:比較関数やフィルタ述語の中で重い処理(正規化・Date化など)を毎回行うと遅い。前計算してから判定すると大幅に速くなることがあります。

デバウンス(入力連打に対する防御)

検索語入力に合わせて毎回フィルタすると重い場合、一定時間入力が止まるまで適用を遅らせます。

function debounce(fn, ms = 200) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
}
JavaScript

よくある落とし穴と回避策(重要ポイントの深掘り)

“条件がOFFでも弾いてしまう”バグ

enabledOnly が false のときに it.active を誤って参照してしまい、OFFでも絞り込まれる。回避は「ONなら適用、OFFなら常にtrue」のパターンを守る。

null/undefined の扱いが曖昧

値系の条件は == null で“欠損なら無視”。厳密なfalseや0を尊重する場面では ?? で既定値を入れつつ比較する。仕様を決めて一貫させる。

AND/OR の混同

複数選択のカテゴリはOR、複数必須条件はAND。UI要件を明文化し、述語合成で表現する。テストで例示ケースを固定化する。

破壊的変更で元配列を壊す

フィルタは filter(非破壊)。並び替えは toSortedslice().sort()。元配列を共有していると副作用の温床になる。


すぐ使えるレシピ(現場の定番コード)

基本フィルタ適用

const applyFilters = (list, f) =>
  list.filter(it =>
    (!f.enabledOnly || it.active) &&
    (!f.category || it.category === f.category) &&
    (f.minPrice == null || it.price >= f.minPrice) &&
    (f.maxPrice == null || it.price <= f.maxPrice)
  );
JavaScript

述語合成ユーティリティ

const and = (...preds) => x => preds.every(p => p(x));
const or  = (...preds) => x => preds.some(p => p(x));
JavaScript

複数カテゴリのOR選択

function whereCategories(selected) {
  if (!selected?.length) return () => true;
  const set = new Set(selected);
  return it => set.has(it.category);
}
JavaScript

検索語の正規化+部分一致

const normalize = s => s.trim().toLowerCase();
const whereQuery = f => {
  if (!f.q) return () => true;
  const q = normalize(f.q);
  return it => normalize(it.name).includes(q);
};
JavaScript

まとめ

フィルタ条件切替の核心は「条件を述語関数として設計し、ON/OFFや値の有無で“適用するかどうか”を明確に制御する」ことです。AND/ORは合成関数で表現し、検索語は正規化してから部分一致。フィルタとソートは層を分け、非破壊で適用する。状態更新と適用処理を分離し、必要なら前計算やデバウンスで性能を安定させる。これを押さえれば、初心者でも読みやすく拡張しやすいフィルタ切替が、実務に耐える形で書けます。

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