Reduxをコードと一緒に学ぶ入門

# Reduxのコード解説

Reduxを使用したサンプルコードを用いて、実行流れと一緒に解説します。
Reduxの入門編としてお使いください。

## 準備

- 基本的なReactのコードは解説しませんので、多少読める必要があります。
- 解説順番の関係でReduxの用語が詳しい説明なしに使われている場合があります。その時は大体の理解で読み流すか、[ここ](https://qiita.com/mgoldchild/items/5be49ea49ebc2e4d9c55)を簡単な用語集として使っても良いです。
- [サンプルコード](https://github.com/NAKKA-K/learn-react-redux)を見れる状態にしておいてください。

## 事前説明

このサンプルコードは**webpack**を使用しています。**webpack**とは、簡潔言うと、複数のファイルを１つにまとめてくれるものです。  
今回のサンプルでは`src/index.jsx`を起点として、`import`で参照されているファイルやその先から参照されるファイルなどが`bundle.js`という1つのファイルにまとめられk`dist/`以下に配置されるようになっています。  
もっと詳しい設定が見たい場合は`webpack.config.js`を見てください。

`index.html`では`<script src="./dist/bundle.js"></script>`というように、webpackで生成した`bundle.js`を読み込んでいます。なので`index.html`では`src/index.jsx`からアクセスされているファイル群の全てを読み込んでいるのと同じ状態になっています。

ここから説明する**React**、**Redux**のコード全ては`src/`以下に格納されており、`src/index.jsx`を起点として実行されます。

このサンプルコードは簡易的な**ToDoリスト**になっています。  
ではここから**本題のReduxの説明**を始めていきます。

## index.jsx

まずindex.jsxの先頭に大量のimport文がありますね。全部完全に理解する必要はありませんが、順番に説明していきましょう。

```js:index.jsx
import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunkMiddleware from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";

import { loadTodos } from "./actions";
import { rootReducer } from "./reducers";
import { App } from "./components/App";

const store = createStore(
    rootReducer,
    composeWithDevTools(applyMiddleware(thunkMiddleware))
);

const view = (
    <Provider store={store}>
        <App />
    </Provider>
);
render(view, document.getElementById("root"));

store.dispatch(loadTodos())
```

---
まず分からないのは12行目にある`createStore`ですね。  
この関数はReduxの仕組み上で重要な**store**を生成する関数です。storeはReduxの仕組み上で1つのみ絶対に生成する必要があるものです。  
使い方は`createStore(reducer, [stateの初期化値], 機能強化関数)`のように使えます。第2引数、第3引数は省略できます。今回は第2引数を省略しています。

```js:index.jsx
const store = createStore(
    rootReducer, // <- 外部ファイルで定義されているreducer
    composeWithDevTools(applyMiddleware(thunkMiddleware))
);

// こういう書き方もできます
const store = createStore(
    rootReducer,
    initialState,
    composeWithDevTools(applyMiddleware(thunkMiddleware))
)
```

`rootReducer`とは、9行目の`import { rootReducer } from "./reducers";`で読み込んでいる`function rootReducer`のことです。

```js:reducer.js
export function rootReducer(state = { isLoading: false, tasks: [] }, action) { ... }
```

`thunkMiddleware`とは**action**の中で非同期関数を実行できるようにするためのミドルウェアです。それを適用するためには`applyMiddleware`という関数で包む必要がある。  
`applyMiddleware`では**dispatch**関数をラッピングし、Actionがreducerに到達する前にミドルウェアがキャッチできるようにするものです。  
その前にある`composeWithDevTools`というのは少し特殊で`ReduxDevTool`というChromeの拡張プラグインを使うためのものです。これを使うとブラウザ上でReduxの内部の流れをデバックトレースできるという便利なものです。  
コードの意味はDevToolミドルウェアを`thunkMiddleware`の外側にくっつけるという意味になっています。

```jsL:index.jsx
const store = createStore(
    rootReducer,
    composeWithDevTools(applyMiddleware(thunkMiddleware)) // <- 機能強化関数(enhancer)
);
```

Chrome拡張の[ReduxDevTool](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)

---

次に17~22行目である以下の部分です。  
まず**Provider**とは`connect()`されたコンポーネントの呼び出しで、Reduxの**store**を使用できるようにするものです。  
現時点ではよく分からない単語などが出てきますが、とりあえずReduxの仕組みにstoreを載せる仕組みだと大まかに理解して先に進みましょう。  
`const view`は単純にReactのコンポーネントです。`components/App.jsx`内の`App`コンポーネントを内部に埋め込んでいます。  
`view`コンポーネントの定義後、HTML要素の`<div id="root"></div>`に対して`view`コンポーネントを描画しています。

```js:index.jsx
const view = (
    <Provider store={store}>
        <App />
    </Provider>
);
render(view, document.getElementById("root"));
```

---

一番最後に書かれているコードの説明です。  
`store`というのは先ほど`createStore()`で生成した変数です。`dispatch()`には**ActionCreator**を引数として実行します。  
今回は`actions.js`で定義した、`const loadTodos`を実行しています。  

- **Action**とはActionの識別子(`type: ""`)と任意の値で構成されたJavaScriptオブジェクトです。  
- **ActionCreator**とはActionを返す関数で**dispatcher**とも呼びます。

dispatchされたActionは**Reducer**に渡されます。ここでいうReducerとは`createStore()`に渡した`rootReducer`のことです。  
`loadTodos` ActionCreatorはそのままActionを返しませんので少し特殊な関数になっています。これが`thunkMiddleware`を追加したことによって書ける書き方です。

```js:index.jsx
store.dispatch(loadTodos())
```

```js
// actions.js
export const setTasks = tasks => ({ type: "SET_TASKS", tasks });
export const addTask = title => ({ type: "ADD_TASK", title });

export const loading = () => ({ type: "LOADING" });
export const loaded = () => ({ type: "LOADED" });

export const loadTodos = () => dispatch => {
    dispatch(loading()); // ネットワーク通信をするため、ロードflagを立てる
    setTimeout(() => {
        fetch("http://192.168.33.10:8080/todos.json")
            .then(res => res.json())
            .then(json => {
                dispatch(setTasks(json.tasks)); // APIから取得したTodosをセットする
                dispatch(loaded()); // ロード終了
            })
            // eslint-disable-next-line
            .catch(console.error);
    }, 3000);
};
```

まず最初に`dispatch(loading());`で別のActionCreatorをdispatchしています。  
これにより`type: "LOADING"`のActionがReducerに渡されロード中flagが立ちます。

その後、APIからtodoの一覧を取得します。今回はテスト用データをローカルファイルとして用意しているため、`setTimeout`で*わざと3秒間の遅延をさせています*。  
取得したjsonは`{ "tasks": [ ... ] }`という構成になっているので、`json.tasks`を`setTasks` ActionCreatorに渡し、dispatchします。これによりstoreにtasksが追加されました。  
ロードが終了したので、`loaded` ActionCreatorをdispatchしています。

次はここで何度も出てきたReducerの中身を解説していこうと思います。

## reducer.js

`rootReducer(state, action)`ではstate引数の初期値を指定しています。  dispatchによって渡されるActionは、`rootReducer()`の第2引数に渡されます。  
関数内ではActionの識別子を元に処理を分岐しています。先ほど実行した`"LOADING"`や`"SET_TASKS"`、`"LOADED"`などがありますね。  
ここで返されたJavaScriptオブジェクトは**storeに渡され、stateが更新されます**。

`...state`というのはJavaScriptオブジェクト(`{ }`)であるstateを展開するというスプレッド演算子という記法です。  
もし`{ state, tasks: action.tasks }`と書いてしまった場合、`{ { isLoading: false, tasks: [] }, tasks: [ ~ ]}`という形式になってしまいます。  
スプレッド演算子で展開すると、`{ isLoading: false, tasks: [], tasks: [ ~ ]}`という形式になり、同じkeyである`tasks`は後ろの値で上書きされます。

簡単に言うと、今までのstateの構造は壊さずに一部だけ更新している、ということですね。

※注: `let lastId = 2;`というコードですが、ちゃんと書くのが面倒だったため書いた簡易的なものです。これが今の**Tasksの最大数**だと仮定してください。


```js:reducers.js
let lastId = 2;

export function rootReducer(state = { isLoading: false, tasks: [] }, action) {
    switch (action.type) {
        case "SET_TASKS":
            return { ...state, tasks: action.tasks };
        case "ADD_TASK":
            return {
                ...state,
                tasks: [...state.tasks, { id: lastId++, title: action.title }]
            };
        case "LOADING":
            return { ...state, isLoading: true };
        case "LOADED":
            return { ...state, isLoading: false };
    }
    return state;
}
```

### 展開の細かい説明

`ADD_TAKS`は少し特殊な書き方になっていますが、よく見て展開してみれば同じように解釈できます。

```js
{ ...state, tasks: [
    ...state.tasks,
    { id: lastId++, title: action.title }
] }
```

上を展開すると、下のようになります。

```js
{ isLoading: false, tasks: [
    { タスク1 },
    { タスク2 },
    { id: 次のタスクID,
      title: Actionによって渡されてた追加タスクのtitle }
] }
```

## components/App.jsx

まず今回見なくてもいい箇所をあげます。`Typography`コンポーネントはMaterialUIというデザイン思考をライブラリにしたものの一部ですので、今回は見た目が変わるだけのものだと思っていてください。

`App`コンポーネントは`index.jsx`からimportされ、Providerで挟まれて描画されますが、見た目としての基盤コンポーネントは`App`コンポーネントになっています。  
ここでは`ConnectedTodoItemList`と`ConnectedNewTodoItem`という2つのコンポーネントが描画されていますが、このコンポーネントはReduxの仕組みにのっとった特殊なコンポーネントです。  

具体的な特殊コンポーネントの話は次の項目でしましょう。

```js:components/App.jsx
import * as React from "react";

import Typography from "@material-ui/core/Typography";

import { ConnectedTodoItemList, ConnectedNewTodoItem } from "../containers";

export const App = () => (
    <div>
        <Typography variant="display1" gutterBottom={true}>
            Todos
        </Typography>
        <ConnectedTodoItemList />
        <ConnectedNewTodoItem />
    </div>
);
```

## container

前項で言った特殊なコンポーネントとは、具体的に言うと`connect()`を使ってラッピングされたコンポーネントのことです。  
`connect()`を使うと通常のコンポーネントはstoreの値を受け取れるようになり、stateやActionCreator(dispatcher)を与えることができます。  

ここではアロー関数の挙動について知っておく必要があります。詳しくは[こちら](https://qiita.com/may88seiji/items/4a49c7c78b55d75d693b)を見て、軽く理解しておいてください。  

最初に`connect()`の説明からしていきます。  
実際のコードではアロー関数を多く使っていますが、この説明コードでは一部のアロー関数を展開して、通常の関数として定義してわかりやすくしています。  

細かい説明は大体コード内のコメントで書いたので、それを読めば大体コードの意味はわかるかと思います。  
コンポーネントを`connect`する理由としては、そのコンポーネント内でActionをdispatchしたい場合、storeに格納されたstateを受け取りたい場合、などが考えられます。  
このコードではstateとコンポーネントに対して渡されたpropsを全てコンポーネントに渡し、`addTask`という関数を渡しています。
実際に渡したstoreの値はコンポーネント内で`this.props.*`という形でアクセスすることになります。  
`addTask`の場合、`this.props.addTask`という形で呼び出します。  
stateはpropsに展開されているので、`this.props.state内の変数名`という形で呼び出せます。  

```js
// 引数にstateとコンポーネントのpropsを受け取ってJavaScriptオブジェクトを返す関数(第2引数は省略できる)
function stateToProps(state, props){
    return { ...state, ...props }
}

// dispatchを引数に受け取ってActionCreatorをdispatchする関数
function dispatcherToProps(dispatch){
    // title引数を受け取って、addTaskをdispatchする関数をアロー関数で定義している
    // 関数をJavaScriptオブジェクトの中に入れている状態
    return { addTask: title => dispatch(addTask(title)) }
}

const TestConnectedComponent = connect(
    stateToProps,
    dispatcherToProps
)(TodoComponent); // storeの値を受け取りたい通常のコンポーネント
```

本題のTodoアプリの`containers.js`に戻りましょう。

ではまず`ConnectedTodoItemList`から見て見ましょう。  
第1引数の`state => state`ですが、これは先ほど見た

> 引数にstateとコンポーネントのpropsを受け取ってJavaScriptオブジェクトを返す関数(第2引数は省略できる)

というものです。  
第2引数は省略して、stateを受け取ってstateを返すだけの関数をアロー関数として定義して渡していますね。  
コンポーネントは`components/TodoItemList.jsx`にある`TodoItemList`を渡していますね。

次に`ConnectedNewTodoItem`です。  
こちらはTodoを追加するコンポーネントなので、いつぞや`reducers.js`で見た`"ADD_TASK"`が必要そうです。タスクを新しく追加する必要がありますからね。  
第1引数は先ほどと同じstateをそのまま返すだけの関数ですね。  
第2引数の`dispatch => ({ addTask: title => dispatch(addTask(title)) })`は、先ほど見た

> dispatchを引数に受け取ってActionCreatorをdispatchする関数

というものです。  
関数内部では`addTask`というkeyで、titleを受け取って新しいタスクを追加する`addTask`というActionCreatorがdispatchされています。  
これは`ConnectedNewTodoItem`コンポーネント内で、Todoを追加するボタンが押された時にその内容をtitle引数として`this.props.addTask(title)`を実行すると、`title => dispatch(addTask(title))`という関数が実行されますね。  

```js:containers.js
import { connect } from "react-redux";

import { TodoItemList } from "./components/TodoItemList";
import { NewTodoItem } from "./components/NewTodoItem";
import { addTask } from "./actions";

export const ConnectedTodoItemList = connect(state => state)(TodoItemList);

export const ConnectedNewTodoItem = connect(
    state => state,
    dispatch => ({ addTask: title => dispatch(addTask(title)) })
)(NewTodoItem);
```

## components/{NewTodoItem.jsx, TodoItemList.jsx}

`containers.js`でimportされていた`NewTodoItem`や`TodoItemList`も軽く見ていきましょう。  
ここまで来ればReactの普通の知識と先ほどまでで出た内容がほとんどですのでもう少しです。  
ここのコードは長いので一部だけ抜粋します。  

`constructor`でstateを定義していますが、これは**storeの持つstateとは全く別物**です。  
stateであることに代わりはありませんが、こちらのstateはこのコンポーネントが独自に持つstateです。  
今回の用途としては、**ユーザーが入力したタスクのタイトルを保存しておく**ためのものです。  

onChangeなどの説明はしませんが、`TextField`で変更されたタスクのタイトルを`NewTodoItem`コンポーネントの内部stateで保存し、更新し続けます。  
そして追加ボタンを押された時にコンポーネント内で定義された`register()`を経由して`this.props.addTask(this.state.title);`を実行しています。  
この`addTask`は`containers.js`で`ConnectedNewTodoItem`コンポーネントに渡されたものです。  
`addTask`は内部で`addTask` ActionCreatorをdispatchしているので、Reducerで`"ADD_TASK"`が実行されることでしょう。  
`"ADD_TASK"`が実行されると、`this.state.title`を元に新しいタスクが作られstoreが持つstateのtasksに新しいタスクが追加されますね。


```js:components/NewTodoItem.jsx
export class NewTodoItem extends React.Component {
    constructor(props) {
        super(props);
        this.state = { title: "" };
    }
    setTitle(title) {
        this.setState({
            title: title
        });
    }
    register() {
        this.props.addTask(this.state.title);
    }
    render() {
        return (
            <div>
                <TextField
                    id="title"
                    label="Title"
                    value={this.state.title}
                    onChange={e => this.setTitle(e.target.value)}
                    margin="normal"
                />
                <Button
                    variant="contained"
                    color="primary"
                    onClick={() => this.register()}
                >
                    ADD
                </Button>
            </div>
        );
    }
}
```

## まとめ

どうでしょうか？  
ここまででReduxの重要な部分の説明は全て終わりました。  
おそらく一度では理解しきれない部分もあったと思いますが、Reduxの用語や概要がわかっている状態からならサンプルコードを元にすぐコードを書き始められるくらいはできるかもしれません。  
コードを書き始めて理解を深めていくか、もう一度この記事を読み直して理解を深めるか、どちらにしても読み始める前と比べるてReduxについての理解が深まっていればこの記事を書いた価値はありました。  

しかし、この記事では目に見える部分を重点的に解説しました。  
Reduxを一度に全て理解するのはほぼ不可能です。  
もっと深い内部のことを知りたければ、別の記事を読むのがいいでしょう。  

この記事はただの入門編です。  
簡単に説明するために、少し実際の挙動とは違う説明の仕方をしたかもしれませんがご了承ください。  

よいReduxライフ(サイクル)を！

## 参考

- [サンプルコードのGitHub](https://github.com/NAKKA-K/learn-react-redux)

- [ReactとReduxちょっと勉強したときの](https://qiita.com/mgoldchild/items/5be49ea49ebc2e4d9c55)

- [JavaScript アロー関数を説明するよ](https://qiita.com/may88seiji/items/4a49c7c78b55d75d693b)