見出し画像

Redux-actionsにコメントつけてみた

本家のURL
https://redux-actions.js.org/

import { createActions, handleActions, combineActions } from 'redux-actions';

const defaultState = { counter: 10 };

const { increment, decrement } = createActions({
  INCREMENT: (amount = 1) => ({ amount }),
  DECREMENT: (amount = 1) => ({ amount: -amount })
});

const reducer = handleActions(
  {
    [combineActions(increment, decrement)]: (
      state,
      { payload: { amount } }
    ) => {
      return { ...state, counter: state.counter + amount };
    }
  },
  defaultState
);

export default reducer;

■ Motivation

Reduxはデータstoreの変異を予測可能にしたが、同時に冗長にもしてしまった。
このツールはその点を考慮して作られたものです。豊富な定型文は書くのもの読むのも苦痛になることがあります。
Action CreatorsとReducersの文字列定数を追跡することは初心者には負担になります。

■API Reference

createAction(s)

createAction

// createActionにtypeを渡すとaction creatorを返す。
createAction(
  type, // stringのみ、入力必須
  payloadCreator = Identity,
  ?metaCreator
)

// 具体例
export const increment = createAction('INCREMENT');
export const decrement = createAction('DECREMENT');

increment(); // { type: 'INCREMENT' }
decrement(); // { type: 'DECREMENT' }
increment(10); // { type: 'INCREMENT', payload: 10 }
decrement([1, 42]); // { type: 'DECREMENT', payload: [1, 42] }

// payloadにError objectが渡されると、redux-actionsは自動でaction.errorをtrueにする。

const noop = createAction('NOOP');

const error = new TypeError('not a number');
expect(noop(error)).to.deep.equal({
  type: 'NOOP',
  payload: error,
  error: true
});

// handleActionかhandleActionsの中ではcreateActionはtypeを返す。
const noop = createAction('INCREMENT');

// As parameter in handleAction:
// noopはtypeとして利用できる
handleAction(noop, {
  next(state, action) {...},
  throw(state, action) {...}
});

// As object key in handleActions:
// noopはtypeとして利用できる
const reducer = handleActions({
  [noop]: (state, action) => ({
    counter: state.counter + action.payload
  })
}, { counter: 0 });

// 単発のActionを作成するためにidentiy formを利用します。
createAction('ADD_TODO')('Use Redux');

// metaCreatorはoptional functionになります。
const updateAdminUser = createAction(
  'UPDATE_ADMIN_USER',
  updates => updates,
  () => ({ admin: true })
);

updateAdminUser({ name: 'Foo' });
// {
//   type: 'UPDATE_ADMIN_USER',
//   payload: { name: 'Foo' },
//   meta: { admin: true },
// }

createActions

createActions(
  actionMap,
  ?...identityActions,
  ?options
)

// 具体例
createActions({
  ADD_TODO: todo => ({ todo }), // payload creator
  REMOVE_TODO: [
    todo => ({ todo }), // payload creator
    (todo, warn) => ({ todo, warn }) // meta
  ]
});

// options
createActions({ ... }, 'INCREMENT', {
  prefix: 'counter', // String used to prefix each type
  namespace: '--' // Separator between prefix and type.  Default: `/`
})
'INCREMENT''counter--INCREMENT'になる

handleAction(s)

handleAction

handleAction(
  type,
  reducer | reducerMap = Identity,
  defaultState,
)

具体例
handleAction(
  'APP/COUNTER/INCREMENT', // type
  (state, action) => ({    // reducer
    counter: state.counter + action.payload.amount
  }),
  defaultState // defaaultState
);

// next(), throw()を明示的に使用する例
// next(): Actionが正常終了した時の処理
// throw(): Actionが異常終了した時の処理

handleAction('FETCH_DATA', {
  next(state, action) {...},
  throw(state, action) {...},
}, defaultState);

handleActions

handleActions(reducerMap, defaultState);

// 具体例
const reducer = handleActions(
  {
    INCREMENT: (state, action) => ({
      counter: state.counter + action.payload
    }),

    DECREMENT: (state, action) => ({
      counter: state.counter - action.payload
    })
  },
  { counter: 0 }
);

// optionsの具体例
const options = {
  prefix: 'counter', // String used to prefix each type
  namespace: '--' // Separator between prefix and type.  Default: `/`
}

createActions({ ... }, 'INCREMENT', options)

handleActions({ ... }, defaultState, options)

combineActions

// 任意の数のActionTypeとActionCreatorsを組み合わせる
// 
combineActions(...types);

// handleActionとcombineActionsの例

const { increment, decrement } = createActions({
  INCREMENT: amount => ({ amount }),
  DECREMENT: amount => ({ amount: -amount }),
})

const reducer = handleAction(combineActions(increment, decrement), {
  next: (state, { payload: { amount } }) => ({ ...state, counter: state.counter + amount }),
  throw: state => ({ ...state, counter: 0 }),
}, { counter: 10 })

expect(reducer(undefined, increment(1)).to.deep.equal({ counter: 11 })
expect(reducer(undefined, decrement(1)).to.deep.equal({ counter: 9 })
expect(reducer(undefined, increment(new Error)).to.deep.equal({ counter: 0 })
expect(reducer(undefined, decrement(new Error)).to.deep.equal({ counter: 0 })
// handleActionsとcombineActionsの例

const { increment, decrement } = createActions({
  INCREMENT: amount => ({ amount }),
  DECREMENT: amount => ({ amount: -amount })
});

const reducer = handleActions(
  {
    [combineActions(increment, decrement)]: (
      state,
      { payload: { amount } }
    ) => {
      return { ...state, counter: state.counter + amount };
    }
  },
  { counter: 10 }
);

expect(reducer({ counter: 5 }, increment(5))).to.deep.equal({ counter: 10 });
expect(reducer({ counter: 5 }, decrement(5))).to.deep.equal({ counter: 0 });
expect(reducer({ counter: 5 }, { type: 'NOT_TYPE', payload: 1000 })).to.equal({
  counter: 5
});
expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 15 });

この記事が気に入ったらサポートをしてみませんか?