やっとReduxが理解できたので、忘れないうちにチュートリアル形式でまとめておきます。
学習の前に
- Node.jsが必要です
- https://nodejs.org/ja/
-
anyenv
からのnodenv
が便利でオススメです
- この記事は、以下の環境で実行しています
- macOS バージョン10.14.6
- Node.js v10.16.0
まず理解すること
-
ReduxとReact-Reduxは違うもの
- Reduxは、設計方法
- React-Reduxは、ReduxとReactをつなぐもの
これを頭に入れておくと、今後の理解がスムーズになるかと思います。
では早速、Reduxを使ってみましょう。
Redux
いったん、Reactのことは忘れてReduxのみに触れていきましょう。
まずはシンプルに使ってみる
適当なディレクトリを作成し、以下を実行してください。
$ npm init -y
$ npm i redux
これで、Reduxが使えるようになりました。
続いて、index.js
を作成し、以下を記述します。
// Reducer
function reducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
};
// Store
const store = require('redux').createStore(reducer);
// Subscribe
store.subscribe(() => console.log(store.getState()));
// Dispatch
store.dispatch({ type: 'INCREMENT' });
Node.js
でindex.js
を実行します。
$ node index.js
// -> 1
ターミナルに1
が表示されれば、成功です。
これで、あなたもReduxが使えるようになりました!
おめでとうございます (^^)
では、Reducer/Store/Subscribe/Dispatch部分を、それぞれ解説します。
Reducer
Reducerは、状態管理したい値(state)と、その値を変化させるための処理が書かれている関数です。
この時点では、まだReduxとはなんの関係もありません。ただの関数です。
function reducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
};
第1引数にstate
、第2引数にaction
を入れ、switch 文を使い、action
の値によって、state
の値を変えてreturn
します。
state
には、状態管理したい値の初期値を代入しておきます。今回の例では、数値の0
を代入しています。
action
は、後に出てくるstore.dispatch({ type: 'INCREMENT'})
メソッドの引数{ type: 'INCREMENT' }
が渡されてきます。
Store
Storeは、state
を管理するためのオブジェクトです。
createStore()
の引数にReducerを入れる事によって作成できます。
詳しくは後述しますが、store.getState()
でstate
の現在の状態を取得したり、store.dispatch()
で状態を変化させたりすることができます。
const store = require('redux').createStore(reducer);
npm i redux
でインストールしたredux
をrequire
し、createStore()
メソッドを呼び出しています。
そして、createStore()
の引数に、上で解説したReducerの関数を入れ、store
定数に代入しています。
store
はオブジェクトになっていて、state
の取得や、その値を変化させることができるstore.dispatch()
を使用することができます。
Subscribe
store.subscribe(() => console.log(store.getState()));
store.subscribe()
メソッドは、store.dispatch()
が呼び出されたら、引数のコールバック関数を実行します。
そして、store.getState()
メソッドは、現在のstate
を取得します。
store.subscribe()
の引数のコールバック関数で、console.log(store.getState())
を実行しているため、store.dispatch()
するたびに、現在のstate
をコンソールに表示します。
Dispatch
store.dispatch({ type: 'INCREMENT' });
store.dispatch()
の引数に入れた値が、Reducerのaction
に渡されます。
よって、上の例では、{ type: 'INCREMENT' }
がreducer
関数のaction
に渡されるので、switch
のaction.type
は'INCREMENT'
の文字列になり、case 'INCREMENT'
が実行され、state + 1
がreturn
されて、state
の値が0から1に変化します。
以下のコメントアウト付きReducerを見ながら、動きを確認してみましょう。
/**
* state に状態管理したい値を代入する。
* store.dispatch({ type: 'INCREMENT' })が実行された場合、
* action には { type: 'INCREMENT' } が入っている。
*/
function reducer(state = 0, action) {
/**
* store.dispatch({ type: 'INCREMENT' })が実行された場合、
* action.type は 'INCREMENT'
*/
switch (action.type) {
case 'INCREMENT':
return state + 1; // こちらが実行され、もともとの state = 0 に +1 されて 1 が return される
default:
return state;
}
}
return
されたstate
は、store
で保持され、store.getState()
で取得することができます。
Actions と Action Creators
Rreducerのcase
で指定した文字列は、通常Actionとして切り分けます。
また、store.dispatch()
の引数の{ type: 'INCREMENT' }
も、Action Creatorsとして、関数にします。
// Actions
const INCREMENT = 'INCREMENT';
// Action Creators
function increment() {
return {
type: INCREMENT
}
}
// Reducer
function reducer(state = 0, action) {
switch (action.type) {
case INCREMENT: // Action の INCREMENT に置き換え
return state + 1;
default:
return state;
}
}
// Store
const store = require('redux').createStore(reducer);
// Subscribe
store.subscribe(() => console.log(store.getState()));
// Dispatch
store.dispatch(increment()); // Action の INCREMENT に置き換え
Actionsとして切り分けたことにより、記述量は増えてしまいますが、ファイルを分けてimport
やexport
するときに、使い勝手が良くなります。
Action Creatorsは、関数の引数のオブジェクトにtype
以外を追加することができるので、Reducerでの処理をコントロールしやすくなります。
たとえば、以下のようにincrement()
関数の引数の数値によって、カウントアップする数値を柔軟に変更させることができます。
// Actions
const INCREMENT = 'INCREMENT';
// Action Creators
function increment(count) { // 引数を追加
return {
type: INCREMENT,
count // 引数を return するオブジェクトに追加する
}
};
// Reducer
function reducer(state = 0, action) {
switch (action.type) {
case INCREMENT:
return state + action.count; // action に count が追加される
default:
return state;
}
};
// Store
const store = require('redux').createStore(reducer);
// Subscribe
store.subscribe(() => console.log(store.getState()));
// Dispatch
store.dispatch(increment(2)); // ここで state は 2 になる
store.dispatch(increment(3)); // さらにここで state は 5 になる
ここまで理解できれば、ReduxのベーシックチュートリアルのData Flowまでは理解できるかと思います。
(英語はがんばってくださいw)
React-Redux
さて、ここまではReduxについて学んできました。
ここからは、いよいよReduxとReactをつなげるReact-Reduxの出番です!
とりあえず、create-react-app
でReactのプロジェクトを作りましょう。
適当なディレクトリで、以下を実行してください。
$ npx create-react-app redux-example
$ cd redux-example && npm start
localhost:3000
でReactのプロジェクトが立ち上がればOKです。
Redux と React-Redux のインストール
ReduxとReact-Reduxをインストールします。
$ npm i redux react-redux
src/reducers.js
とsrc/actions.js
を作成し、Reduxを学んだときに作成した、index.js
のActions、Action CreatorとReducer部分をそれぞれコピペして、少し修正します。
src/actions.js
// Actions
export const INCREMENT = 'INCREMENT'; // exportして外部で使用できるように修正
// Action Creators
export function increment(count) { // exportして外部で使用できるように修正
return {
type: INCREMENT,
count
}
};
src/reducers.js
import { INCREMENT } from './actions'; // Actionsをimport
// Reducer
export default function reducer(state = 0, action) { // export defaultして外部で使用できるように修正
switch (action.type) {
case INCREMENT:
return state + action.count;
default:
return state;
}
};
Storeはsrc/index.js
に書いてみましょう。ここでReact-Reduxを使い、ReactとReduxをつなぎます。
以下のように、src/index.js
を書き換えてみてください。
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'; // createStoreをimport
import { Provider } from 'react-redux'; // Providerコンポーネントをimport
import reducer from './reducers'; // reducer関数をimport
import App from './App';
const store = createStore(reducer); // storeオブジェクトを作成
ReactDOM.render(
// <App />コンポーネントを<Provider store={store}></Provider>でラップする
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
<Provider store={store}></Provider>
でラップされたコンポーネント内であれば、Storeにアクセスすることができます。
これで、React上でReduxを使う準備が整いました。
続いて、Redux Hooksを使い、state
の取得と変更をしてみましょう!
Redux Hooks
Redux Hooksの登場により、簡単にReduxのStoreにアクセスできるようになりました。src/App.js
でuseSelector
とuseDispatch
を使用し、stateの取得と変更をしてみましょう。
src/App.js
を以下のように書き換えてみてください。
src/App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux'; // これがRedux Hooks
import { increment } from './actions'; // Action Creatorsのincrement関数をimport
function App() {
const count = useSelector(state => state); // useSelector で state を取得
const dispatch = useDispatch(); // useDispatch で state を更新
return (
<div className="App">
{/* 現在のstateを表示 */}
<div>{count}</div>
{/* クリックするたびに、stateを更新 */}
<button onClick={() => dispatch(increment(1))}>INCREMENT</button>
</div>
);
};
export default App;
localhost:3000
にstate
の値が表示されているかとい思います。また、その下の「INCREMENT」ボタンをクリックすると、数値が書き換わるはずです。
useSelector
を使うと、store
からstate
を取得することができます。後述する、useDispatch
によるdispatch()
が実行されると、state
の比較が行われ、変更があれば再レンダリングされます。
Reduxのときに出てきた、store.subscribe(() => console.log(store.getState()));
をやっている感じですね。
useDispatch
を使うと、store.dispatch()
にアクセスすることができます。
こちらも、Reduxのときに出てきた、store.dispatch(increment());
ですね。
おわりに
かなりシンプルでしたが、ReduxとRedux-Reactのチュートリアルでした。
ここまで理解できれば、**ReduxとReact-Redux**のドキュメントはだいぶ理解できるようになるかと思います。まだまだ解説していない部分もありますので、そこは一緒に学習していきましょう。
少しでも、皆様の学習の手助けになれば、これ幸いです。
それでは、また。