Redux stateにimmutable.jsを使う

immutable.jsをRedux stateに使ってみました。本記事は簡単な使い方の紹介になります。
[Immutable collections for JavaScript]
(https://immutable-js.github.io/immutable-js/)


Immutabilityはアプリのパフォーマンスに貢献します。加えてプログラミングやデバッグをよりシンプルにしてくれます。

ImmutabilityとReduxの関連は、以下の記事に詳しく書いてあります。
[Redux FAQ: Immutable Data](https://redux.js.org/faq/immutable-data)



# Todoアプリ


[React Redux](https://react-redux.js.org/)にあるTodoアプリにimmutable.jsを導入してみました。


環境は簡単に構築できます。

```
yarn create react-app todo-immutable
cd todo-immutable
yarn add redux react-redux classnames
yarn add immutable
rm -rf src
```


4個のファイルを修正しました。必要に応じて、オリジナルと比べてみてください。ざっくり言って、コードが短くなっています。


state.todosをMapのListとして利用していきます。

```JSX:src/redux/reducers/todos.js
import { ADD_TODO, TOGGLE_TODO } from "../actionTypes";
// ListとMapを使います
import { List, Map } from "immutable";

export default function(state = List(), action) {
  switch (action.type) {
    case ADD_TODO: {
      const { id, content } = action.payload;
      return state.push( // ListにMapを追加
        Map({
          id,
          content,
          completed: false
        })
      );
    }

    case TOGGLE_TODO: {
      const { id } = action.payload;
      return state.map(todo => { // Listのmapで処理
        if (todo.get("id") === id) {
          return todo.update("completed", v => !v); // Mapのupdate
        }
        return todo;
      });
    }

    default:
      return state;
  }
}
```


ListのfilterやfilterNotを使ってセレクタ関数を定義します。Mapのtodoにはtodo.get()でアクセスします。


```JSX:src/redux/selectors.js
import { VISIBILITY_FILTERS } from "../constants";

export const getTodosByVisibilityFilter = (store, visibilityFilter) => {
  switch (visibilityFilter) {
    case VISIBILITY_FILTERS.COMPLETED:
      // Listのfilter関数。Mapのget関数。
      return store.todos.filter(todo => todo.get("completed"));
    case VISIBILITY_FILTERS.INCOMPLETE:
      // ListのfilterNot関数。Mapのget関数。
      return store.todos.filterNot(todo => todo.get("completed"));
    case VISIBILITY_FILTERS.ALL:
    default:
      return store.todos;
  }
};
```




toObject()でMapをJSON Objectに変換します。


```JSX:src/components/Todo.js
import React from "react";
import { connect } from "react-redux";
import cx from "classnames";
import { toggleTodo } from "../redux/actions";

const Todo = ({ todo, toggleTodo }) => {
 // toObject()でMapをJSON Objectに変換。
 const { id, content, completed } = this.props.todo.toObject();
 return (
  <li className="todo-item" onClick={() => toggleTodo(id)}>
    {todo && completed ? "〇" : "✖"}{" "}
    <span
      className={cx(
        "todo-item__text",
        todo && completed && "todo-item__text--completed"
      )}
    >
      {content}
    </span>
  </li>
  );
}

// export default Todo;
export default connect(
  null,
  { toggleTodo } // mapDispatchToProps を Object として定義
)(Todo);
```


Mapのget関数でidを取得します。

```JSX:src/components/TodoList.js
import React from "react";
import { connect } from "react-redux";
import Todo from "./Todo";
import { getTodosByVisibilityFilter } from "../redux/selectors";
import { VISIBILITY_FILTERS } from "../constants";

const TodoList = ({ todos }) => (
  <ul className="todo-list">
    {todos && todos.size
      ? todos.map((todo, index) => {
          // Mapのget関数でidを取得
          return <Todo key={`todo-${todo.get("id")}`} todo={todo} />;
        })
      : "No todos, yay!"}
  </ul>
);


const mapStateToProps = state => {
  const { visibilityFilter } = state;
  const todos = getTodosByVisibilityFilter(state, visibilityFilter);
  return { todos };
};
// export default TodoList;
export default connect(mapStateToProps)(TodoList);
```


今回は以上です。