lodashの便利関数を使いこなす:実務で頻出のユーティリティ集

概要

lodash は JavaScript のユーティリティライブラリとして長く使われてきました。ES2015 以降の言語仕様の充実で「もう不要では」と語られることも増えましたが、実務のコードを読んでいると、標準機能では地味に面倒な処理を lodash が肩代わりしている場面はまだ多く残っています。

この記事では、実務で頻出する lodash の関数を「なぜネイティブより便利なのか」「どこで使うと効くのか」という観点で整理します。標準機能で十分なものは正直に「標準で足りる」と書き、lodash を使う判断基準もあわせて示します。

環境

  • Node.js 18 以降 / モダンブラウザ
  • lodash 4.x
  • TypeScript 併用

必要な関数だけを個別 import する前提で書きます。

import get from "lodash/get";
import groupBy from "lodash/groupBy";
import debounce from "lodash/debounce";

import _ from "lodash" で全体を取り込むとバンドルサイズが膨らむため、フロントエンドでは特に個別 import を基本にします。

実務で効くユーティリティ

1. get / set — ネストされたオブジェクトの安全なアクセス

API レスポンスの深い階層に触れるとき、途中が undefined だと例外になります。オプショナルチェーンで大半は解決しますが、パスを動的に組み立てたいケースでは get が便利です。

import get from "lodash/get";

const res = { user: { profile: { name: "Taro" } } };

const name = get(res, "user.profile.name", "名無し"); // "Taro"
const age = get(res, "user.profile.age", 0);          // 0(欠損)

// パスを動的に組み立てられるのが最大の利点
const key = "profile.name";
const dynamic = get(res, ["user", ...key.split(".")]); // "Taro"

オプショナルチェーン(res?.user?.profile?.name ?? "名無し")で書ける場合はそちらで十分です。パスが実行時に決まるときにだけ get を選ぶ、という判断が実務的です。

2. groupBy / keyBy — コレクションの整形

一覧データをキーで束ねる処理は、集計・表示の前段で頻出します。

import groupBy from "lodash/groupBy";
import keyBy from "lodash/keyBy";

const orders = [
  { id: 1, status: "paid", amount: 1200 },
  { id: 2, status: "pending", amount: 800 },
  { id: 3, status: "paid", amount: 500 },
];

const byStatus = groupBy(orders, "status");
// { paid: [ {id:1}, {id:3} ], pending: [ {id:2} ] }

const byId = keyBy(orders, "id");
// { "1": {id:1,...}, "2": {...}, "3": {...} }

groupBy は reduce で自前実装できますが、毎回書くと読み手の負担になります。「配列 → 辞書」への変換は頻度が高いので、ここは lodash に任せる価値があります。なお keyBy はキーが文字列化されるため、数値 ID を保ちたいなら new Map(orders.map(o => [o.id, o])) を検討してください。

3. debounce / throttle — イベント制御の定番

検索入力のインクリメンタルサーチや、スクロール・リサイズ処理の間引きは、自前実装するとタイマー管理でバグりがちです。ここは lodash が今なお強い領域です。

import debounce from "lodash/debounce";

const onSearch = debounce((q: string) => {
  fetchSuggestions(q);
}, 300);

// React の場合、再レンダーで再生成されないよう useMemo でくるむ
const debounced = useMemo(
  () => debounce((q: string) => fetchSuggestions(q), 300),
  []
);

// アンマウント時のキャンセルを忘れない
useEffect(() => () => debounced.cancel(), [debounced]);

leading / trailing / maxWait といったオプションが揃っている点が、自前実装との差です。throttle は「一定間隔で必ず実行」なのでスクロール連動 UI に向きます。

4. cloneDeep — 深いコピー

structuredClone が使える環境が増えたため、まずはそちらを検討します。ただし関数や一部の特殊オブジェクトを含むデータでは structuredClone が例外を投げるため、cloneDeep が無難な場面が残ります。

import cloneDeep from "lodash/cloneDeep";

const original = { nested: { list: [1, 2, 3] } };
const copy = cloneDeep(original);
copy.nested.list.push(4);
// original は影響を受けない

判断基準: JSON 相当のデータなら structuredClone。それ以外や既存コードとの一貫性を優先するなら cloneDeep。

5. isEqual — 深い等価比較

オブジェクト同士の中身が同じかを判定したいとき、JSON.stringify の比較はキー順序に依存して壊れます。isEqual は構造を再帰的に比較します。

import isEqual from "lodash/isEqual";

isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // true(キー順に依存しない)
isEqual([1, [2, 3]], [1, [2, 3]]);       // true

深い比較はコストがあるため、多用は避けて浅い比較で済む設計を優先します。

6. uniqBy / sortBy / orderBy — 重複排除と並び替え

import uniqBy from "lodash/uniqBy";
import orderBy from "lodash/orderBy";

const users = [
  { id: 1, name: "A", age: 30 },
  { id: 1, name: "A2", age: 25 },
  { id: 2, name: "B", age: 40 },
];

uniqBy(users, "id"); // [ {id:1,name:"A"}, {id:2,name:"B"} ](先勝ち)

orderBy(users, ["age", "name"], ["desc", "asc"]); // 複数キー+方向指定

単純な uniq は [...new Set(arr)] で済みますが、キー関数付きの重複排除は標準にないため uniqBy が効きます。orderBy は複数キー+方向指定が一発で書ける点が読みやすさに直結します。

標準機能で足りるもの(あえて使わない判断)

lodash を惰性で使わないために、標準で十分なものを明示しておきます。

やりたいこと 標準で足りる書き方
配列の変換 map / filter / reduce
単純な重複排除 [...new Set(arr)]
存在チェック arr.includes(x)
平坦化 arr.flat() / arr.flatMap()
既定値の補完 ?? / ?.
オブジェクトの一部抽出 分割代入 + 残余

これらに lodash を使うと、依存とバンドルサイズを増やすだけになります。

確認方法

  • import した関数を実際に呼び、期待した戻り値になるかを単体テストで確認します
  • バンドルサイズは webpack-bundle-analyzer などで、lodash 全体を取り込んでいないか可視化します
  • TypeScript では @types/lodash を入れ、get の戻り値型が any になりがちな点をキャストで補います

注意点

  • 全体 import を避ける: import _ from "lodash" は tree-shaking が効きにくいため、個別 import か lodash-es を使います
  • get の乱用は型を失う: 静的にパスが分かるならオプショナルチェーンの方が型安全です
  • 深い比較・深いコピーはコスト: isEqual / cloneDeep はデータ量に比例して重くなります
  • ネイティブで代替可能なものは代替する: 言語仕様は年々充実しています。定期的に「これはもう標準で書けないか」を見直すのが健全です

まとめ

lodash は「全部入れて全部使う」時代から、「標準で書けないところだけ選んで使う」時代に移りました。実務で今も効くのは、get(動的パス)、groupBy / keyBy(コレクション整形)、debounce / throttle(イベント制御)、uniqBy / orderBy(キー付き操作)あたりです。

「便利だから使う」ではなく「標準で足りるか」を毎回問い、足りないところにだけ差し込む。この使い分けができると、コードは読みやすくバンドルは軽いまま、lodash の恩恵だけを受け取れます。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。