Edited at

最小の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から見ることができる

となります。


処理の流れ

自分の理解では、このソースの上から下に処理が流れていくイメージになります。

捉え方によっては、もしかしたら下から上になるかもしれないです。


最後に

あくまで自分の理解なので、間違っている部分や伝わりづらい部分があるかと思いますが、その点はご了承ください。

写経だけでなく、自分で作るって大事だなぁと思いました。(小並感)