FluxによるReactアプリの状態管理　Redux・React-Redux編

Redux公式サイトのチュートリアルで学習した内容をまとめたものです。
基本的には、本家サイトに記載されている内容を自分なりに日本語でまとめなおしたものです。
2019年5月時点の情報を基にしています。

[React](https://ja.reactjs.org/)の基本事項は既に習得されている方向けです。

# 概要
## Reduxとは
Reduxは、JavaScriptアプリケーションの状態管理を容易にする為のフレームワークです。
主にReactを用いて構築されたアプリケーションで利用されますが、その他のフレームワークやライブラリーで構築された場合も、もちろん利用できます。

アプリケーション上で表示されるデータや、各コンポーネントの表示非表示の状況を一元的に管理することで、見通しのよいアプリケーションの構築を実現させようというものです。
Reactの提供元であるFaceBookが提唱した、Fluxという設計パターンからヒントを得て作られています。

[Redux](https://redux.js.org/)

## React-Reduxとは
名前のとおり、ReactコンポーネントとReduxを接続する為のフレームワークです。
React・Reduxに依存した造りというわけではないため、[Angular](https://angular.jp/)や[Vue](https://jp.vuejs.org/index.html)などのその他のフレームワークや[VanillaJS](http://vanilla-js.com/)での利用も可能とのことです。

[React Redux](https://react-redux.js.org/)

## Fluxとは
Reactによるアプリケーション構築を保管する為に考案された、設計パターンです。
データの流れを一方通行にすること、アプリケーションの状態「State」を一元的に管理することで、アプリケーションの管理が煩雑化することを防ぐ試みです。
Fluxによるアプリケーションは、下記のパートで構成されます。

- Dispatcher： アプリケーションへの更新情報を集約・通知
- Store： アプリケーションの状態を保持・管理
- Action: アプリケーションの更新情報を得る為の内部API　
- View： Reactコンポーネントによるインターフェース

Dispatcherは、アプリケーションに1つだけ存在します。
Storeは、アプリケーションの規模などに応じて1つ以上存在します。
Actionは、アプリケーションに発生しうる変更の数だけ存在します。
Vewは、Storeと接続したコンポーネントを頂点としてツリー構造を形成します。

詳細はこちらの記事で扱っております。
[FluxによるReactアプリの状態管理　Flux・FluxUtils編](https://qiita.com/koedamon/items/6bca2375feec89c66359)

本家サイトはこちら。
[Flux](https://facebook.github.io/flux/)

# インストール
npmのパッケージとして公開されています。

```sh
npm install redux react-redux --save
```

[introduction > installation | Redux](https://redux.js.org/introduction/installation)
[Itroduction > Quick Start | React Redux](https://react-redux.js.org/introduction/quick-start)

# Todoアプリ
本家サイトのベーシックチュートリアルには、Todoアプリの解説とソースコードがあります。
ちょくちょく更新されているようです。
2019年5月に写経したソースはこちらです。
[todoapp-flux-redux](https://github.com/BeeBow6/todoapp-flux-redux)
[動作のサンプル](https://beebow6.github.io/todoapp-flux-redux/dist/index.html)

[ReactのJSX記法](https://ja.reactjs.org/docs/introducing-jsx.html)を利用する為、[Babel](https://babeljs.io/)などのトランスパイラを利用します。
[webpack](https://webpack.js.org/)などのモジュールバンドラーの利用も前提としています。
今回は、[Parcel](https://parceljs.org/)を利用しています。

# Actions
Actionは、Storeに対して送信するデータです。
実体はJavaScriptのオブジェクトです。
Actionの種類を示す、`type`プロパティを必ず持っています。`type`プロパティの値は、一般的に文字列が用いられます。
アプリケーションの規模が大きい場合は、予め定数として定義しておくことが望ましいでしょう。
その他Storeに送信したいデータが含まれます。

```js:新規Todoの追加時のAction
// ActionType
const ADD_TODO = 'ADD_TODO'

// Action
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}
```

このActionを生成するのがActionCreatorです。
ReduxにおけるActionCreatorは、単純にActionを返すのみです。

```js:actions/index.js
let nextTodoId = 0;

/**
 * TODOリストを追加する
 */
export const addTodo = text => ({
  type: 'ADD_TODO',
  id: nextTodoId++,
  text
});

/**
 * 表示フィルターをかける
 */
export const setVisibilityFilter = filter => ({
  type: 'SET_VISIBILITY_FILTER',
  filter
});

/**
 * 完了未完了切り替える
 */
export const toggleTodo = id => ({
  type: 'TOGGLE_TODO',
  id
});

/**
 * フィルターの定数
 */
export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
};
```

この戻り値をStoreに送信するには、後述するStoreの`dispatch()`メソッドを利用します。

```js
store.dispatch(addTodo('Learn about Redux'))
```

[Actions | Redux](https://redux.js.org/basics/actions)

# Reducers
Actionはあくまで「何が起きたか」を定義するだけです。それを受けて「どうするか」に関する具体的な指示は含みません。
その責務を負うのがReducerです。Reducerは、Storeに送信されたActionをもとに、Storeが保持するアプリケーションのStateを、どう変更するかを決めます。

[Reducers | Redux](https://redux.js.org/basics/reducers)

## アプリケーションのStateの設計
Reduxでは、アプリケーションの状態「State」は一つのオブジェクトで表されます。
今回のTodoアプリのStateは、以下のようになります。

```js
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      id: 1,
      text: 'Write a article about Redux',
      completed: true
    },
    {
      id: 2,
      text: 'Go to the Cats cafee',
      completed: false
    }
  ]
}
```

## Stateを変更する
Reducerは、[副作用](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0))のない純粋な関数です。
Storeから呼び出され、引数で以前のStateと新たに生成されたActionを受け取り、新しいStateを返します。

```js
(previousState, action) => newState
```

`previousState`と`action`の内容が同一であれば、何度呼び出しても同じ`newState`を返します。
その為、Reducerには下記のルールがあります。

- 引数を変更してはいけません。
- APIの呼び出しやルーティングなどの、副作用が発生する処理を記述してはいけません。
- 純粋でない関数を呼び出してはいけません。（例：Date.now(), Math.random()）

上記Stateを変更するStateは下記のようになります。

```js
import { VisibilityFilters } from './actions';

const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos:[]
};

function todoApp(state = initialState, action){
  switch(action.type){
    case 'ADD_TODO':
      return Object.assign({}, state, {
        todos: [/* 項目追加 */]
      });
    case 'TOGGLE_TODO':
      return // 項目切替後newState
    case 'SET_VISIBILITY_FILTER':
      return {
        visibilityFilter: action.filter
      };
    default:
      return state;
  }
}
```

初回呼び出し時、引数`state`は`undefined`の状態で渡されます。
その為、[デフォルト引数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Default_parameters)には、Stateの初期値を設定しています。
ActionTypeでActionの種類が「追加」であることを識別し、以前StateにActionの情報を加味した新しいStateを作成しています。
Stateを変更する必要がない場合は、以前のStateをそのまま返します。
Reactは、Stateの変更を確認した上で再描画を実行しています。
その際の比較方法は値ではなく、オブジェクト参照元の比較です。
その為、Reducer内でStateを変更する場合は、引数で受け取った以前のStateをそのまま変更してはいけません。
[Object​.assign()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)や[スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)等を利用して、予めStateを複製した上で変更する必要があります。

[Reduxユーザーが最もハマるstateの不正変更とその検出方法](https://blog.mitsuruog.info/2018/02/why-is-immutability-required-by-redux)
[Why is immutability required by Redux? | Redux](https://redux.js.org/faq/immutable-data#why-is-immutability-required-by-redux)

## Reducerの分割
アプリケーションの規模が大きい場合、StateおよびReducerのサイズも大きくなってきます。
その場合、一つのReducerで管理することが難しくなる為、適宜分割することが望ましいでしょう。

`todos`と`visibilityFilter`、それぞれ管理するReducerに分割します。
初期値も、それぞれの項目の初期値のみ用意します。

```js:reducers/todos.js
const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
};

export default todos;
```

```js:reducers/visibilityFilter.js
import { VisibilityFilters } from '../actions';

const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter;
    default:
      return state;
  }
};

export default visibilityFilter;
```

## Reducerの結合
分割したものの、Storeから参照されるReducerは一つだけです。
そのため、各ReducerはReduxが提供する`combineReducers()`関数を利用して、一つに束ねます。
この関数は各Reducerから返されるStateのパーツを、一つのオブジェクトにまとめたStateにして返す、RootReducerを生成します。

```js
import { combineReducers } from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';

export default combineReducers({
  todos,
  visibilityFilter
});

// [参考]自作した場合
function roodReducer(state = {}, action){
  return {
    todos: todos(state.todos, action),
    visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  };
}
```

[combineReducers(reducers) | Redux](https://redux.js.org/api/combinereducers)

# Store
アプリケーションのStateを一元的に管理するモジュールです。
アプリケーションに1つだけ存在します。
下記の責務を負います。

- アプリケーションの現在のStateを保持する
- `getState()`メソッドを通じて、Stateを提供する
- `dispatch(action)`メソッドを通じて、Stateの更新を可能にする
- `subscribe(listener)`メソッドを通じて、Stateの更新イベントのリスナーを登録する
- `subscribe(listener)`メソッドの戻り値として、イベントリスナー解除用関数を提供する

Fluxでは独立したモジュールとして存在したDispatcherは、Storeのメソッドになりました。
実行順序の調整などは行いません。
Storeは、Actionの受付とState更新の通知を行いますが、新しい状態を生成することはしません。
それはReducerの責務です。
`dispatch()`が実行されると、受け取ったActionとStoreが保持している現在のStateをReducerに渡し、新しいStateを受け取ります。

Reduxで提供している`createStore`関数に、RootReducerを渡して作成します。

```js
import { createStore } from 'redux';
import rootReducer from './reducers/';

const store = createStore(rootReducer);

// 第二引数に初期Stateを渡すことも可能
const store = createStore(rootReducer, initialState);
```

基本的にはこれだけです。こちらで何か処理を書くことはありません。

[Store | Redux](https://redux.js.org/basics/store)

# ReduxとReactの接続
## StoreとReactComponentの接続
StoreとViewを、React-Reduxが提供している`Provider`コンポーネントを利用して繋げます。
Reduxで用意したstoreを`Provider`コンポーネントにpropsとして渡します。

```jsx
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers/';
import App from './components/app';

const store = createStore(rootReducer);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
```

これで、View側がStoreの持つStateを参照する為の準備が出来ました。

## アプリケーション内でStoreのデータを共有する
Reactでは基本的に、propsを通じて親コンポーネントから子孫コンポーネントへとデータを受け渡します。
コンポーネントの階層が深くなると、データのバケツリレーが発生してしまいます。
React-Reduxが提供している`connect`関数を利用することで、Storeとの接点を持つ`Provider`コンポーネントへのショートカットを作ります。

```jsx
import { connect } from 'react-redux';
import { someActionA, someActionB } from '../actions';

// 子孫コンポーネントに必要なStateを抽出（任意）
const mapStateToProps = ({paramA, paramB}) => {
  return {
    paramA,
    paramB
  }
};

// 子孫コンポーネントに渡すコールバック関数を定義（任意）
const mapDispatchToProps = dispatch => {
  return {
    onClick: data => dispatch(someActionA(data)),
    onChange: data => dispatch(someActionB(data))
  };
};

// ChildComponentをラップしたコンポーネントを作成
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ChildComponent);

// Stateの抽出や、コールバック関数の定義が不要な場合
export default connect()(ChildComponent);


// 下位コンポーネントはpropsで受け取れる
const ChildComponent = ({paramA, onClick, ...otherProps})=>{
  return (
    <div>
      <button onClick={e=> onClick(paramA)}>{paramA}<button>
      <GrandchildComponent {...otherProps} />
    </div>
  );
};
```

`connect()`関数の部分は[カリー化](https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96)が含まれていてややこしいですが、`connect()`の戻り値は関数です。それをさらに実行すると、Reactのコンポーネントが作成されます。
これにより、`ChildComponent`はpropsとしてStateの全データを受け取ることが出来ます。

一部分だけ渡したい場合は、`mapStateToProps`関数を`connect()`の第一引数に渡します。
この関数はStateが更新されるたびに呼ばれ、引数でStateを受け取ります。
そして、戻り値が下位のコンポーネントへpropsとして渡されます。
第二引数の`mapDispatchToProps()`は、StoreにActionを送信する処理を定義したい場合に利用します。
`Store.dispatch()`メソッドをを引数で受け取るので、ActionCreatorと共にコールバック関数を定義します。
この関数は初回に実行され、以降は戻り値がpropsの一つとして下位コンポーネントに渡されます。

## PresentationalComponents と ContainerComponents
ReactとReduxでアプリケーションを構築する際は、基本的にコンポーネントをPresentationalComponentとContainerComponentの2種に分けます。
PresentationalComponentはビジネスロジックを持たず、描画に徹します。
ContainerComponentはReduxと接点を持ち、PresentationalComponentに必要なpropsを受け渡します。
こうすることで、より見通しの良いアプリケーションの構築を可能とし、コンポーネントの再利用性を高めます。

||Presentational Components|Container Components|
|:--|:--|:--|
|目的|どのように見せるか|どのように動くか|
|Reduxを意識<br>しているか|しない|する|
|データの提供元|propsから|ReduxのStateから|
|データの変更|propsの<br>コールバック関数を実行|Reduxのメソッドを実行|
|作成方法|自分で|通常は、<br>ReactReduxを利用して|

[Presentational and Container Components | Redux](https://redux.js.org/basics/usage-with-react#presentational-and-container-components)

### TodoListの場合
Todoリストを表示するコンポーネントのツリーは下記のとおりです。
VisibleTodoListがContainerComponentsです。

```
Provider
  └ VisibleTodoList ☆
      └ TodoList
          └ Todo
```

Stateからtodoリストの配列を取り出し、さらに現在のフィルターにマッチするものを抽出して、下位のコンポーネントに渡しています。
また、項目をクリック時に完了状況を切り替えるためのコールバック関数も定義しています。

```js:visibleTodoList.jsx
import { connect } from 'react-redux';
import { toggleTodo } from '../actions';
import TodoList from '../components/todoList';
import { VisibilityFilters } from '../actions';

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case VisibilityFilters.SHOW_ALL:
      return todos;
    case VisibilityFilters.SHOW_COMPLETED:
      return todos.filter(t => t.completed);
    case VisibilityFilters.SHOW_ACTIVE:
      return todos.filter(t => !t.completed);
    default:
      throw new Error('Unknown filter' + filter);
  }
};

const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
};

const mapDispatchToProps = dispatch => {
  return {
    toggleTodo: id => dispatch(toggleTodo(id))
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList);
```

Todo項目のコンポーネントは、受け取った情報を表示したり、コールバック関数を実行するだけです。

```jsx:todo.jsx
const Todo = ({ onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
  >
    {text}
  </li>
);
```

### AddTodoの場合
Todoに項目を追加するコンポーネントを構築します。
Form要素を含むコンポーネントなど、責務の分離が難しい場合は、分離しないという選択肢もあります。
`mapStateToProps()`を利用しない場合は全Stateを受け取ることになるので、[分割代入](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)を利用して必要分だけ受け取っています。

```jsx
import React from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../actions';

let AddTodo = ({ dispatch }) => {
  let input;

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault();
          if (!input.value.trim()) {
            return;
          }
          dispatch(addTodo(input.value));
          input.value = '';
        }}
      >
        <input ref={node => (input = node)} />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  );
};

export default connect()(AddTodo);
```

[Designing Other Components | Redux](https://redux.js.org/basics/usage-with-react#designing-other-components)


# イメージ図
無謀にも、自分なりにA4にまとめてみました。
![TodoList_Redux.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362737/b3de8184-3319-d853-3537-df739702cb13.png)