JavaScript Tips | 配列ユーティリティ:条件変換

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「条件変換」

「条件変換」は、配列の中の「条件に合う要素だけ」を変換し、それ以外はそのまま残す処理です。
英語で言うと mapIfmapWhenconditionalMap みたいなイメージです。

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

特定ステータスのレコードだけフラグを書き換えたい。
金額がマイナスの明細だけを 0 に補正したい。
特定ユーザーだけ表示名を変えたいが、他はそのままにしたい。

普通の map は「全部の要素を変換する」前提ですが、
条件変換は「一部だけ変換して、残りはそのまま」というところがポイントです。


基本形:条件と変換関数を受け取る mapIf

まずは「条件に合うときだけ変換する」関数

一番素直な形は、「変換するかどうかを決める条件」と「実際の変換処理」を分けて受け取るユーティリティです。

function mapIf(array, predicate, mapper) {
  if (!Array.isArray(array)) {
    return [];
  }

  if (typeof predicate !== "function" || typeof mapper !== "function") {
    return array.slice();
  }

  return array.map((item, index) => {
    if (predicate(item, index)) {
      return mapper(item, index);
    }
    return item;
  });
}
JavaScript

ここでの重要ポイントをかみ砕きます。

配列でなければ空配列を返す(安全側)。
predicate(条件)か mapper(変換)が関数でなければ、そのままコピーして返す。
map の中で「条件に合うときだけ mapper を適用し、合わないときは元の要素をそのまま返す」。

つまり、「条件に合う要素だけ変換し、それ以外はそのまま」という動きになります。

動作イメージ

例えば、「0 未満の数値だけ 0 に補正したい」場合。

const nums = [10, -5, 0, 20, -3];

const normalized = mapIf(
  nums,
  (n) => n < 0,          // 条件:負の数
  (n) => 0               // 変換:0 にする
);
// [10, 0, 0, 20, 0]
JavaScript

map だけで書こうとすると「負のときは 0、それ以外は n」という if を毎回書くことになりますが、
mapIf にすると「条件」と「変換」がきれいに分かれます。


よくある業務パターンでの条件変換

ステータスに応じてフラグを変換する

例えば、次のような配列があるとします。

const items = [
  { id: 1, status: "active", archived: false },
  { id: 2, status: "deleted", archived: false },
  { id: 3, status: "active", archived: false },
];
JavaScript

status: "deleted" のものだけ archived: true にしたい」場合。

const updated = mapIf(
  items,
  (item) => item.status === "deleted",
  (item) => ({ ...item, archived: true })
);

/*
[
  { id: 1, status: "active",  archived: false },
  { id: 2, status: "deleted", archived: true  },
  { id: 3, status: "active",  archived: false },
]
*/
JavaScript

ここでのポイントは、「削除済みだけ変換」「他はそのまま」という意図が、
predicatemapper にきれいに分かれていることです。

特定ユーザーだけ表示名を変える

const users = [
  { id: 1, name: "Taro" },
  { id: 2, name: "Hanako" },
  { id: 3, name: "Admin" },
];

const masked = mapIf(
  users,
  (u) => u.name === "Admin",
  (u) => ({ ...u, name: "[管理者]" })
);

/*
[
  { id: 1, name: "Taro" },
  { id: 2, name: "Hanako" },
  { id: 3, name: "[管理者]" },
]
*/
JavaScript

「Admin だけ表示名を変える」という要件を、
条件と変換に分けて素直に書けます。


条件と変換を「名前付き関数」にすると一気に読みやすくなる

条件に名前を付ける

条件が業務ルールそのものなので、名前付き関数にしておくと意図が伝わりやすくなります。

function isDeleted(item) {
  return item.status === "deleted";
}

function isNegative(n) {
  return n < 0;
}
JavaScript

変換にも名前を付ける

変換も同じく、「何をしたいか」を名前にします。

function archive(item) {
  return { ...item, archived: true };
}

function toZero() {
  return 0;
}
JavaScript

組み合わせて使う

const archivedItems = mapIf(items, isDeleted, archive);
const normalizedNums = mapIf(nums, isNegative, toZero);
JavaScript

これだけで、「削除済みをアーカイブする」「負の数を 0 にする」という意図が、
コードを読んだ瞬間に伝わります。


条件変換と普通の map の違いを整理する

普通の map

map は「全部の要素を変換する」前提です。

const doubled = nums.map((n) => n * 2);
JavaScript

全要素に対して同じ変換をかけるときは、これで十分です。

条件変換(mapIf)

mapIf は、「条件に合う要素だけ変換し、それ以外はそのまま」です。

const normalized = mapIf(nums, (n) => n < 0, () => 0);
JavaScript

「一部だけ変えたい」「他は触りたくない」というときに、
map で if を書くより、mapIf のほうが意図がはっきりします。


インデックスや複数条件を使った条件変換

インデックスを使う

predicatemapper は、第 2 引数にインデックスも受け取れます。

const data = ["A", "B", "C", "D"];

const withStarOnOdd = mapIf(
  data,
  (value, index) => index % 2 === 1,
  (value) => value + "*"
);
// ["A", "B*", "C", "D*"]
JavaScript

「奇数番目だけマークを付ける」といった変換も、同じユーティリティで書けます。

複数条件を組み合わせる

例えば、「アクティブかつ金額が 10,000 以上のものだけ、ラベルを付けたい」場合。

function isActive(item) {
  return item.status === "active";
}

function isLargeAmount(item) {
  return item.amount >= 10000;
}

function markAsImportant(item) {
  return { ...item, important: true };
}

const marked = mapIf(
  items,
  (item) => isActive(item) && isLargeAmount(item),
  markAsImportant
);
JavaScript

条件を小さな関数に分けて、それを組み合わせることで、
「どんな条件のときにどう変換しているか」が、仕様レベルで見えるようになります。


条件変換ユーティリティで意識してほしいポイント

「変えるもの」と「変えないもの」をはっきり分ける

条件変換の本質は、「変えるもの」と「変えないもの」を明確に分けることです。

条件に合うものだけ mapper を適用し、
条件に合わないものはそのまま返す。

このパターンをユーティリティにしておくと、
「一部だけ変えたい」という処理がすべて同じ形で書けるようになります。

非破壊的にする(新しい配列を返す)

mapIfmap を使っているので、元の配列は変更されません。
これは意図的です。

元の配列をいじらないことで、「どこで何が変換されたのか」が追いやすくなります。
業務コードでは、「元データはそのまま」「変換結果を別で持つ」というスタイルのほうが、デバッグしやすく、バグも減ります。

条件・変換を小さく分けて再利用する

条件(predicate)と変換(mapper)を小さな関数に分けておくと、
別の場所でも同じルールを再利用できます。

isActive
isDeleted
isNegative

archive
toZero
markAsImportant

こういう小さな部品を増やしていくと、
「業務ルールのライブラリ」を自分で持っているような感覚になってきます。


手を動かして感覚をつかむ

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

function mapIf(array, predicate, mapper) {
  if (!Array.isArray(array)) {
    return [];
  }
  if (typeof predicate !== "function" || typeof mapper !== "function") {
    return array.slice();
  }
  return array.map((item, index) =>
    predicate(item, index) ? mapper(item, index) : item
  );
}

const nums = [10, -5, 0, 20, -3];
const normalized = mapIf(nums, (n) => n < 0, () => 0);

const items = [
  { id: 1, status: "active", amount: 5000 },
  { id: 2, status: "deleted", amount: 15000 },
  { id: 3, status: "active", amount: 20000 },
];

function isDeleted(item) {
  return item.status === "deleted";
}

function archive(item) {
  return { ...item, archived: true };
}

const archivedItems = mapIf(items, isDeleted, archive);
JavaScript

「どの要素が変換されて、どの要素がそのままか」「条件や変換を変えると結果がどう変わるか」を、自分の目で確かめてみてください。

そのうえで、自分のプロジェクトに

export function mapIf(...) { ... }
JavaScript

を置き、「配列の一部だけを条件付きで変換したくなったら、必ずこの“条件変換ユーティリティ”を通す」と決めてみてください。
それだけで、「map の中に if がべったり書かれたコード」から、一段整理された設計に進めます。

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