比對兩個 object 的 property 是否相等

Ramda 的 where() 可產生多個以 && 串起來的 Predicate,但若針對 Object,且都是 === 的 Predicate,就可以使用 whereEq() 更為精簡。

Version


VS Code 1.31.1
Quokka 1.0.136
Ramda 0.26.1

filter()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { filter } from 'ramda';

const data = [
{ id: 1, title: 'Impatient JavaScript', year: 2018 },
{ id: 2, title: 'RxJS in Action', year: 2017 },
{ id: 3, title: 'Speaking JavaScript', year: 2014 }
];

const item = { title: 'RxJS in Action', year: 2017 };

const getBooks = filter(
x => x.title === item.title && x.year === item.year
);

const result = getBooks(data);
console.dir(result);

想要找出 title 等於 RxJS in Actionyear 等於 2017 的 data,且條件以 object 方式呈現。

11 行

1
2
3
const getBooks = filter(
x => x.title === item.title && x.year === item.year
);

使用了 Ramda 的 filter() 並搭配 predicate function,由於包含了兩個條件,因此必須使用 && 串起來。

whereeq000

where()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { filter, where, equals, prop } from 'ramda';

const data = [
{ id: 1, title: 'Impatient JavaScript', year: 2018 },
{ id: 2, title: 'RxJS in Action', year: 2017 },
{ id: 3, title: 'Speaking JavaScript', year: 2014 }
];

const item = { title: 'RxJS in Action', year: 2017 };

const getBooks = filter(where({
title: equals(prop('title', item)),
year: equals(prop('year', item))
}));

const result = getBooks(data);
console.dir(result);

既然 predicate 是多條件,最標準的寫法就是使用 where(),再搭配 equals()prop()

where()
{String: (* → Boolean)} → {String: *} → Boolean
將眾多 predicate 以 object 形式整理,產生 && 串起眾多 predicate

{String: (* → Boolean)}String 為 property 的 key,表示該 predicate 針對此 property;(* → Boolean) 為 predicate

{String: *}:data 為 object

Boolean:若符合 predicate 則傳回 true,否則傳回 false

由於 where() 的 currying 特性,只要提供第一個參數,就會產生 {String: *} → Boolean 的 predicate 供 filter()find() … 等 operator 使用

whereeq003

whereEq()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { filter, whereEq } from 'ramda';

const data = [
{ id: 1, title: 'Impatient JavaScript', year: 2018 },
{ id: 2, title: 'RxJS in Action', year: 2017 },
{ id: 3, title: 'Speaking JavaScript', year: 2014 }
];

const item = { title: 'RxJS in Action', year: 2017 };

const getBooks = filter(whereEq(item));

const result = getBooks(data);
console.dir(result);

使用 where() 雖然可行,但還是略顯麻煩,Ramda 特別提供了 whereEq()

whereEq()
{String: *} → {String: *} → Boolean
比對兩個 object 的 property 是否相等

{String: *}:Spec object

{String: *}:Test object

Boolean:若相等傳回 true,否則傳回 false

Spec object 的 property 可以少於 Test object

whereeq001

compose()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { filter, whereEq, compose } from 'ramda';

const data = [
{ id: 1, title: 'Impatient JavaScript', year: 2018 },
{ id: 2, title: 'RxJS in Action', year: 2017 },
{ id: 3, title: 'Speaking JavaScript', year: 2014 }
];

const item = { title: 'RxJS in Action', year: 2017 };

const getBooks = compose(
filter,
whereEq(item)
)();

const result = getBooks(data);
console.dir(result);

原本 11 行

1
const getBooks = filter(whereEq(item));

先執行 whereEq(),再將結果傳給 filter() 執行,因此也可以將 whereEq()filter() 兩個 function 加以組合。

11 行

1
2
3
4
const getBooks = compose(
filter,
whereEq(item)
)();

與一般 compose() 不同的是:最後還加上了 (),因為 whereEq(item) 不需要任何參數,所以直接以 () 結束。

whereEq(item) 產生的 predicate 傳給 filter() 後,還留一個參數,這正是要傳給 getBooks() 的 data,因為 Point-free 而省略。

whereeq002

Conclusion


  • 當 predicate 有多個條件,可使用 where()
  • 當 predicate 有多個條件,且資料來自於 object,並且只要判斷 === 而已,則可使用 whereEq()
  • 實務上不見的要使用 compose() 重構,filter(whereEq(item)) 的寫法可讀性已經很高

Reference


Ramda, filter()
Ramda, where()
Ramda, whereEq()
Ramda, compose()