伝えたいこと

  • Lodash.jsは最高
  • Lodash.jsを使うとコードがシンプルになる
  • Lodash.jsはメソッドチェインを使わないと魅力80%減
  • Lodash.jsは最高

Lodash.jsとは?

https://lodash.com/

  • JavaScriptユーティリティライブラリ
  • つまり便利なライブラリ
  • 週2000万ダウンロードという超人気ライブラリ(jQueryは200万、Reactは500万)
  • Underscore.jsより高速かつ高機能

使い方

まずnpmパッケージインストール。

npm i lodash
# または
yarn add lodash

それを読み込んで_(アンダーバー)で使う。

import _ from 'lodash'

const result = _.compact([0, 1, false, 2, '', 3])
console.log(result) // [1, 2, 3]

undefined/NaN対策

JavaScriptを書いていると苦労するのがundefinedやNaN対策。

let arr;
console.log(arr.length) // Error

let obj;
console.log(obj.hoge) // Error

let num;
console.log(1 / num) // NaN

エラーにならないように下記のような回避コードを書くことが多かった。

let arr;
console.log(Array.isArray(arr) ? arr.length : 0) // 0

let obj;
if ( typeof obj === 'object' && obj !== null && !Array.isArray(obj) ) {
  console.log(obj.hoge)
} else {
  console.log(undefined) // undefined
}

let num;
console.log(num > 0 ? 1 / num : 1) // 1

Lodash.jsならこの問題を下記のようにシンプルに解決できる。

let arr;
console.log(_.size(arr)) // 0

let obj;
console.log(_.get(obj, "hoge")) // undefined

let num;
console.log(_.divide(1, num)) // 1

もちろん、ちゃんとエラーを返してほしいケースもあるし、Lodash.jsの関数が返す返り値が微妙と感じるなら使わないほうがいいが、ほとんどのケースではLodash.jsを使ったほうがコードの可読性が上がる。

メソッドチェイン

Lodash.jsはメソッドチェインという使い方ができる。
例えば下記のように、ある配列に対して様々な操作をしたい場合、無駄なコードが多くDRYではない。

let result = [1,2,3,4,5]
result =  _.concat(result, [3,4,5,6,7]) // 別の配列を結合
result = _.uniq(result) // 重複する要素を削除
result = _.shuffle(result) // ランダムにシャッフル
console.log(result) // [6,2,7,1,3,5,4]

このコードにメソッドチェインを用いると下記のように書ける。

const result = _.chain([1,2,3,4,5])
  .concat([3,4,5,6,7])
  .uniq()
  .shuffle()
  .value()
console.log(result) // [6,2,7,1,3,5,4]

メソッドチェインは可読性が上がるだけでなく、下記メリットがある。

  • 変数再代入が発生しない
  • 変数の数を減らせる
  • 副作用が起こりにくい

つまり、関数型プログラミングのようなメリットを享受できるのが特徴。

よく使う機能

特によく使う機能をいくつかご紹介。

_.compact

配列の中からfalsyな要素(0, false, ""など)をすべて除外してくれる

_.compact([0, 1, false, 2, '', 3])
// => [1, 2, 3]

_.difference

2つの配列のうち第一引数の配列にしかない要素を残す

_.difference([4, 2, 1], [2, 3])
// => [1, 4]

_.differenceBy

2つの配列を関数またはプロパティ名で比較して第一引数の配列にしかない要素を残す

_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor)
// => [1.2]

const arr1 = [{ 'x': 2 }, { 'x': 1 }]
const arr2 = [{ 'x': 1 }, { 'x': 3 }]
_.differenceBy(arr1, arr2, 'x')
// => [{ 'x': 2 }]

_.intersection

2つの配列の中で共通している要素を残す

_.intersection([2, 1], [2, 3])
// => [2]

_.union

2つの配列を重複のない状態で結合する

_.union([2], [1, 2])
// => [2, 1]

_.xor

2つの配列の中で共通している要素を除外し、片方にしか存在しない要素を残す

_.xor([2, 1], [2, 3])
// => [1, 3]

_.uniq

配列の中の重複する要素を1つだけ残して除外する

_.uniq([2, 1, 2])
// => [2, 1]

_.without

配列の中から指定した値を除外する

_.without([2, 1, 2, 3], 1, 2)
// => [3]

_.flattenDeep

ネストされた配列をすべてフラットにする

_.flattenDeep([1, [2, [3, [4]], 5]])
// => [1, 2, 3, 4, 5]

_.cloneDeep

オブジェクトを複製する(ネストされたオブジェクトも含めて全て値渡しする)

const obj1 = { 'a': 1, 'b': { 'c': 2 } }
const obj2 = obj1
obj2.b.c = 3
console.log(obj1.b.c) // 3

const obj3 = { 'a': 1, 'b': { 'c': 2 } }
const obj4 = _.cloneDeep(obj3)
obj4.b.c = 3
console.log(obj3.b.c) // 2

_.isPlainObject

純粋なオブジェクト型かどうかを判定する

_.isPlainObject([1, 2, 3])
// => false

_.isPlainObject({ 'x': 0, 'y': 0 })
// => true

_.isPlainObject(Object.create(null))
// => true

_.round

指定した桁数で四捨五入する

_.round(4.006)
// => 4

_.round(4.006, 2)
// => 4.01

_.round(4060, -2)
// => 4100

_.range

指定した要素数でカウントアップまたはカウントダウンする配列を生成

_.range(4)
// => [0, 1, 2, 3]

_.range(-4)
// => [0, -1, -2, -3]

_.range(1, 5)
// => [1, 2, 3, 4]

_.range(0, 20, 5)
// => [0, 5, 10, 15]

_.range(0, -4, -1)
// => [0, -1, -2, -3]

_.range(1, 4, 0)
// => [1, 1, 1]

_.range(0)
// => []

_.debounce

関数が呼び出されてから指定した秒数以内に再度関数が呼び出された場合は経過秒数をリセットし、関数を実行しない

function handleScroll () {
  console.log("handleScroll()")
}
const _handleScroll = _.debounce(handleScroll, 2000)
window.addEventListener('scroll', _handleScroll)