多分これが一番小さいと思います。
前置き
この記事は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構成を作ってみました。
ソースコード
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 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
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成分を含む)箇所があります。
<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を繋いでいます。
// NewComponent = connect(Componentからdispatchされたアクション) (Component)
const App = connect(
state => state
)(Container);
Provider(後述)に渡されたStore(後述)を、自分が定義したReact Component(今回はContainerクラス)のthis.propsに入れています。
説明は最後のrender
でしたほうがわかりやすいので、ここではあまり説明しません。
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
// Reducerの戻り値を新しい状態(State)としてStoreで管理する
const store = createStore(reducer);
createStore
関数はReduxの関数です。
上で説明したReducerによって作られたStateを、Store内で管理します。
よくわからないですが、Reduxではそういう決まりみたいです。
render()
// Root Render
render (
<Provider store={ store }>
<App />
</Provider>,
document.getElementById("root")
);
これもReactをやっている方なら大体わかるかと思います。
違うところは、Providerでconnect
関数によって作られたAppをくるんでいるところですね。
このProviderはReact-Redux成分で、前述したconnect
関数とセットで、ReactとReduxを繋ぐ役割があります。
データの流れは、
- Providerのstoreに渡された値が、中のAppに渡る
- Appに渡ってきたデータが
connect
関数に渡る -
connect
関数では、受け取った値をそのまま返す - すると、Containerのthis.propsにデータが入る
- Container(React Component)内ではthis.propsから見ることができる
となります。
処理の流れ
自分の理解では、このソースの上から下に処理が流れていくイメージになります。
捉え方によっては、もしかしたら下から上になるかもしれないです。
最後に
あくまで自分の理解なので、間違っている部分や伝わりづらい部分があるかと思いますが、その点はご了承ください。
写経だけでなく、自分で作るって大事だなぁと思いました。(小並感)