【React】Immutable.js から Immer へ切替


なぜ

ずっと immutable.js 使ってますが、OOP アプローチに関して、最新何かあるかを探したことろ、immer が見つかった。なんと、React open source award 2020 の Breakthrough of the year を受賞されたようです。

どっちが良い

現時点、どっちもメジャーな OOP アプローチです。immutable.js は Facebook が管理するライブラリで、結構年数ありました。過去2年のダウンロード数から見ると、最近は immer が波に乗ってます。サイズやパフォーマンス的にも immer が良さそうです。

個人感想のメリット

immutable.jsredux 用のライブラリで、immerReact.setState にも使えます。
type の定義が必要なくて、楽になって、コード量は大きく減ってませんが、可読性が向上したと思います。

変更点 - Settings

redux-immutableconnected-react-routerimmutable ライブラリが必要なくなります。

変更点 - Reducer

reducer から見ると、import のみ変わって、ほとんど修正必要ありません。

reducer-immutable.js
import { handleActions, Action } from 'redux-actions';
import { AppState } from '@domains';
import { ActionTypes } from '@constants';
import { App01Payload, App02Payload } from '@actions/app';

const app = handleActions<AppState, any>(
  {
    [ActionTypes.APP_PLUS_REQUEST]: (state: AppState) => state,
    [ActionTypes.APP_PLUS_SUCCESS]: (state: AppState, { payload: { num } }: Action<App01Payload>) => state.plus(num),
    [ActionTypes.APP_PLUS_FAILURE]: (state: AppState) => state,

    [ActionTypes.APP_MINUS_REQUEST]: (state: AppState) => state,
    [ActionTypes.APP_MINUS_SUCCESS]: (state: AppState, { payload: { num } }: Action<App02Payload>) => state.minus(num),
    [ActionTypes.APP_MINUS_FAILURE]: (state: AppState) => state,
  },

  new AppState()
);

export default app;
reducer-immer
import { handleActions, Action } from 'redux-actions';
import { AppState } from '@domains';
import { ActionTypes } from '@constants';
import { App01Payload, App02Payload } from '@actions/app';

const app = handleActions<AppState, any>(
  {
    [ActionTypes.APP_PLUS_REQUEST]: (state: AppState) => state,
    [ActionTypes.APP_PLUS_SUCCESS]: (state: AppState, { payload: { num } }: Action<App01Payload>) => state.plus(num),
    [ActionTypes.APP_PLUS_FAILURE]: (state: AppState) => state,

    [ActionTypes.APP_MINUS_REQUEST]: (state: AppState) => state,
    [ActionTypes.APP_MINUS_SUCCESS]: (state: AppState, { payload: { num } }: Action<App02Payload>) => state.minus(num),
    [ActionTypes.APP_MINUS_FAILURE]: (state: AppState) => state,
  },

  new AppState()
);

export default app;

変更点 - ロジック

従来の typescript type 定義が省略できるので、少し楽になります。書き方も immutable.js 独自の方法じゃなく、ES6 が書けます。

immutable.js
import { Record } from 'immutable';

export interface IApp extends AppProps, Record<AppProps> {
  get<K extends keyof AppProps>(key: K): AppProps[K];
}

export interface AppUIProps {}

export interface AppProps extends AppUIProps {
  // count
  count: number;
}

/**
 * App
 */
export default class AppState extends Record<AppProps>({
  count: 0,
}) {
  plus(num: number) {
    return this.set('count', this.count + num);
  }

  // inner update
  minus(num: number) {
    return this.set('count', this.count - num);
  }
}
immer
import { immerable, produce } from 'immer';

export default class AppState {
  [immerable] = true;

  count: number = 0;

  plus = (num: number) =>
    produce(this, (draft) => {
      draft.count += num;
    });

  minus = (num: number) =>
    produce(this, (draft) => {
      draft.count -= num;
    });
}

おまけ

immer公式ドキュメントは詳しく色んな情報書いています。

immer 取り込み済みのプロジェクトは GitHub 公開中です。