LoginSignup
7
9

More than 5 years have passed since last update.

ReduxのReducerでネストしたstateのマージを行う

Last updated at Posted at 2017-05-19

たとえば,複数のユーザのフレンド・フォロワーを,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

  }
}

ただ,...自体は安全でもnullundefinedからメソッドチェインすると例外飛ぶのつらい…実際

  • (((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

  }
}

だいぶ見やすくなったね!

7
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9