たとえば,複数のユーザのフレンド・フォロワーを,stateとして以下のような構造で保持するストアを考えます。
{
"@user_xxx": {
"friends": [
"@user_yyy",
"@user_zzz"
],
"followers": [
"@user_yyy"
]
},
"@user_yyy": {
"friends": [],
"followers": [
"@user_xxx"
]
}
}
フレンド・フォロワーだけを個別にページネートして取得するAPIが存在する場合,既に取得している他の情報を消してしまわないように,ネストしたstateのマージを行う必要があります。最初書いたコードは以下のようになりました。
actions.js
export const FETCH_FOLLOWERS_SUCCESS = 'FETCH_FOLLOWERS_SUCCESS'
reducer.js
import { FETCH_FOLLOWERS_SUCCESS } from './actions'
export default (state = {}, action) => {
switch (action.type) {
case FETCH_FOLLOWERS_SUCCESS:
return {
...state,
[action.params.screenName]: {
...(state[action.params.screenName] || {}),
followers: [
...((state[action.params.screenName] || {}).followers || []),
...action.payload
]
}
}
default:
return state
}
}
うわあ,とてつもなく読みにくい…しかし
...
演算子はオブジェクトではないものに使ってもエラーを吐かず,空オブジェクトと同じように扱われる
という特性を知ったので,以下のように書けることが判明しました。
reducer.js
import { FETCH_FOLLOWERS_SUCCESS } from './actions'
export default (state = {}, action) => {
switch (action.type) {
case FETCH_FOLLOWERS_SUCCESS:
return {
...state,
[action.params.screenName]: {
...state[action.params.screenName],
followers: [
...(state[action.params.screenName] || {}).followers,
...action.payload
]
}
}
default:
return state
}
}
ただ,...
自体は安全でもnull
やundefined
からメソッドチェインすると例外飛ぶのつらい…実際
(((a || {}).b || {}).c || {}).d
a && a.b && a.b.c && a.b.c.d
で何とかなるけど増えてくるとつらい…というときには,このような関数を作っておくと幸せになれそうです。
utils.js
export const safe = (lambda, ...args) => {
try {
return lambda(...args)
} catch (e) {}
}
reducer.js
import { safe } from './utils'
import { FETCH_FOLLOWERS_SUCCESS } from './actions'
export default (state = {}, action) => {
switch (action.type) {
case FETCH_FOLLOWERS_SUCCESS:
return {
...state,
[action.params.screenName]: {
...state[action.params.screenName],
followers: [
...safe(() => state[action.params.screenName].followers),
...action.payload
]
}
}
default:
return state
}
}
だいぶ見やすくなったね!