Help us understand the problem. What is going on with this article?

最小のReact+Redux

More than 3 years have passed since last update.

多分これが一番小さいと思います。

前置き

この記事はReactチュートリアルを終え、そのままReduxに入門した初心者が書いています。
もし間違っていることがあれば教えていただけると幸いです。

普通にチュートリアル見て分かる人は見る必要ないと思います。

試した環境

  • OS:CentOS release 6.7 (Final)
  • Node.jsバージョン:v6.1.0

Reduxわからん

Reactチュートリアルを終え、jQueryやVanillaから仮想DOMに置き換えるとなんかすごいわかりやすい!感動!となっていたところ、なにやらReduxなるものがあるということを耳にする。

データの流れを一方向にする?確かにわかりやすそうだ!早速やってみよう!と公式やQiita等で検索して写経してみたものの、Action, Reducer, container等のよくわからない単語ばっかりでさっぱりでした。

ActionがReducerでReducerはAction + State => NewStateで……

わからなさすぎたので、いろいろな場所から情報をかき集めてきて最小のReact+Redux構成を作ってみました。

ソースコード

index.jsx
import React, { Component } from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";


// React Component
class Container extends Component {
    render() {
        return (
            <div>
                <button onClick={ e => {
                    this.props.dispatch (
                        // ReducerにActionをディスパッチする
                        { type: "INCREMENT" }
                    );
                    console.log("pushed button");
                }}>
                    button
                </button>
                <div>
                    { this.props.value }
                </div>
            </div>
        );
    }
};
// NewComponent = connect(Componentからdispatchされたアクション) (Component)
const App = connect(
    state => state
)(Container);


// Reducer
const reducer = (state = { value: 0 }, action) => {
    // Componentの中でディスパッチされたActionがaction変数に入ってくる
    // action = { type: "INCREMENT" }
    switch (action.type) {
        case "INCREMENT":
            // valueに+1して返す
            return Object.assign({}, { value: state.value + 1 });
        default:
            return state;
    }
}


// Reducerの戻り値を新しい状態(State)としてStoreで管理する
const store = createStore(reducer);


// Root Render
// ReduxのProviderコンポーネントで、Appコンポーネント
// (connect関数にComponentを渡して作成したNewComponent)をラップし、Storeを渡す
// (簡単に言えば)propsにStateが入る
render (
    <Provider store={ store }>
        <App />
    </Provider>,
    document.getElementById("root")
);

やってること

画面にボタンと初期値「0」が表示され、ボタンを押すたびに数字がインクリメントされます。

解説

import文

import文
import React, { Component } from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";

見ての通りですが、React・Redux・React-Reduxから必要なものをimportしています。
それぞれの使い方は後から出てくるので、そちらを見てください。

React Component

ReactComponent
class Container extends Component {
    render() {
        return (
            <div>
                <button onClick={ e => {
                    this.props.dispatch (
                        // ReducerにActionをディスパッチする
                        { type: "INCREMENT" }
                    );
                    console.log("pushed button");
                }}>
                    button
                </button>
                <div>
                    { this.props.value }
                </div>
            </div>
        );
    }
};

こちらはReact入門までやった人ならなんとなくわかるのではないでしょうか?
ES2015シンタックスであるclass構文を使っていますが、React.createClassと大体同じです。
同じくArrow functionsも使用していますが、こちらについては各自ググってください。(丸投げスタイル)
実行すると(大体)下記のようなDOMが生成されます。

実行結果
<div>
    <button>button</button>
    <div>0</div>
</div>

JSXに書いてあった内容ほぼそのままですね。
(this.props.value == 0として書いてます。)

ただし、1箇所Reactではない(Redux成分を含む)箇所があります。

Redux成分
<button onClick={ e => {
    this.props.dispatch (
        // ReducerにActionをディスパッチする
        { type: "INCREMENT" }
    );
}}>

この部分です。

コード内のコメントの通り、Actionのディスパッチを行なっています。
ボタンがクリックされたら(onClick)、dispatch関数を呼ぶようになっています。
dispatch関数の引数にAction(=JavaScriptのオブジェクト)を渡すと、後述のReducerにActionが渡されます(ディスパッチされます)。

Actionにはtypeを含める必要があります。
このtypeはReducerで処理を分けるためにあるので、かぶらなければなんでもいいです。

connect

connect関数は、React-Reduxの関数です。ReactとReduxを繋いでいます。

connect関数
// NewComponent = connect(Componentからdispatchされたアクション) (Component)
const App = connect(
    state => state
)(Container);

Provider(後述)に渡されたStore(後述)を、自分が定義したReact Component(今回はContainerクラス)のthis.propsに入れています。
説明は最後のrenderでしたほうがわかりやすいので、ここではあまり説明しません。

Reducer

Reducer
// Reducer
const reducer = (state = { value: 0 }, action) => {
    // Componentの中でディスパッチされたActionがaction変数に入ってくる
    // action = { type: "INCREMENT" }
    switch (action.type) {
        case "INCREMENT":
            // valueに+1して返す
            return Object.assign({}, { value: state.value + 1 });
        default:
            return state;
    }
}

この関数は、2つの引数を受け取ります。

  • state …… 現在のState
  • action …… React ComponentからディスパッチされたAction

Reducerでは、「必要なデータ」と「現在のState」を使って「やりたいこと」を実行する処理です。
action内のtypeによって処理を分けます。

Actionというのは、「やりたいこと(type)」と「必要なデータ」をひとまとまりにしたものです。
「やりたいこと」をswitch文で判断して、適切な処理を実行します。
「やりたいこと」が終わると、新しいStateが出来上がります。

これが「Action + State => NewState」の正体です。

(2016/06/06 修正・追記)

コメントで教えていただいたのですが、現在のstateから新しいstateを作成するので、(Redux的に)正しくは下記のようになります。

修正前
return Object.assign({}, { value: action.value + 1 });

修正後
return Object.assign({}, { value: state.value + 1 });

現在の状態はstateに持っているので、そちらの値を使用するのがいいようです。
dispatch関数でvalueを渡す必要がなくなり、より簡潔になりました。
(この修正により、サンプルコードの中で、Actionの「必要なデータ」はなくなりました。)

createStore

Store生成
// Reducerの戻り値を新しい状態(State)としてStoreで管理する
const store = createStore(reducer);

createStore関数はReduxの関数です。
上で説明したReducerによって作られたStateを、Store内で管理します。
よくわからないですが、Reduxではそういう決まりみたいです。

render()

render処理
// Root Render
render (
    <Provider store={ store }>
        <App />
    </Provider>,
    document.getElementById("root")
);

これもReactをやっている方なら大体わかるかと思います。
違うところは、Providerでconnect関数によって作られたAppをくるんでいるところですね。

このProviderはReact-Redux成分で、前述したconnect関数とセットで、ReactとReduxを繋ぐ役割があります。

データの流れは、

  1. Providerのstoreに渡された値が、中のAppに渡る
  2. Appに渡ってきたデータがconnect関数に渡る
  3. connect関数では、受け取った値をそのまま返す
  4. すると、Containerのthis.propsにデータが入る
  5. Container(React Component)内ではthis.propsから見ることができる

となります。

処理の流れ

自分の理解では、このソースの上から下に処理が流れていくイメージになります。
捉え方によっては、もしかしたら下から上になるかもしれないです。

最後に

あくまで自分の理解なので、間違っている部分や伝わりづらい部分があるかと思いますが、その点はご了承ください。
写経だけでなく、自分で作るって大事だなぁと思いました。(小並感)

Chayata
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした