コードで理解するRedux(React使用)
を理解するために、自分なりの解釈をまとめた記事です。
Redux追加 環境作成
npm install --save-dev redux react-redux
Redux三原則をもっと簡単にしてみる
①コンポ単位でstate管理するのだりーよ!一個に集約しようぜ!
Storeという管理者におまかせするようにした。
②state触りたいならちゃんと手順踏んでね
バグが特定しやすくなる。
③reducerは同じデータなら毎回同じデータを返すこと
すいません、ここだけ理由ができていません。
登場人物と役割
参考
Redux + React (Example: Todo List) の構成図
Reduxでコンポーネントを再利用する
説明 | |
---|---|
Store (ストア) |
stateの管理者。 アプリ内で一つしか存在しない。 |
Provider (プロバイダ) |
Storeと他コンポーネントの仲介役。 react-reduxが提供するコンポーネントで、アプリ内で一つしか存在しない。 |
Container (コンテナ) |
stateの変更通知を受け取って、propsを子に渡してあげるのが主な目的。 渡すpropsには、さり気なくdispatch(後述)を仕込むのがミソ。 |
Presentatinal (プレゼンテーション) |
描画特化。 ①propsを使ってDOMの描画。 ②ユーザから要求がアレば、propsとして受け取った関数の呼び出し。 ただコレだけを行う。 |
Reduxの流れを知る
まず、なんとか理解してほしいこと
- state(画面の状態)はStoreが管理する
- stateの変更はStoreにある「Reducer」という場所で行う
- Reducerはdipatchという行為で実行される(つまり、dispatchする=Reducerの起動)
- dispatchをするにはActionと呼ばれる物が必要(処理分岐で使うだけです)
- Reducerは新しいstateを用意するのでコンポーネントはそれを描画してあげる
今まで出てきた言葉を使って、Reduxをする
①画面でユーザーの要求が発生(stateの変更要求)
②Containerにあるイベントが実行
③イベントはReducerを起動するためにdispatchする(Actionの作成もここで)
④Reducerはstate(要求時の状態)とActionから、新しいstate(要求後の状態)をContainerに渡す
⑤Containerはstateをpropsに変換して、画面で描画する
⑥終わり
コード
ボタン押すたびにカウントアップするだけの処理。
役割ごとにファイルで分けてみました(コレがベストかは別にして)
処理の流れを踏まえた、実装までのコーディング方法
目的 | どこで | なにを |
---|---|---|
React下準備 | React描画部 | ReactDOM.renderで指定できるところを用意。 |
Store作成 | Store | createStore関数でStoreを作成。 引数で必要なReducerとstate初期化関数は、中身がなくてもいいので定義しちゃいましょう。 |
Providerを用意 | Provider監視部 | 作成したStoreをProviderのプロパティにセットして、JSXに記載。 |
メインViewを用意 | Presentatinal | 中身はなくてもいいので、イベントハンドラ定義まで進める。 |
イベントハンドラでよぶ関数を準備 | Container | stateが変わるなら、Reducerに仕事をお願いしないといけないので、関数内でdispatchの呼び出しを実装。 |
Actionの作成 | Container | dispatchの呼び出しに必要なので、関数を定義。 |
Reducerの実装 | Store | 作成途中だったReducerに、Actionと既存のstateから、どう新しいstateを返すか定義。 |
新しいstateをpropsに変換 | Container | 関数実装。 |
コンテナのイベントを呼び出す | Presentatinal | イベントハンドラ内でContainerで定義したイベントを追記。 |
変換したpropsを用いて描画 | Presentatinal | JSXに追記。 |
(state = props)の初期値に対応 | Store | state初期化関数の中身実装。 |
PresentatinalとContainerの紐付け | Container | connect関数の実装。 |
コンテナをProviderの監視下に | Provider監視部 | JSXにContainerを追記。 |
実例
React描画部
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Provider監視部
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import store from './Store.js';
import Container from './Container.js';
//①唯一のStoreを含むProviderが、コンポーネントを包むように記載
//②stateを切り離されたコンポーネントたちを下層で定義
ReactDOM.render(
<Provider store={store}>
<Container />
</Provider>
,
document.getElementById('root')
);
Store
import { createStore } from 'redux'
//Reducer
function reducer(state, action) {
//Actionに応じて、処理分岐
switch(action.type) {
case 'ACT1':
//新しいstateを作成
//return先は、コンテナのprops変換処理かな?(未確認)
return (
Object.assign(
{} //空のオブジェクトに以下を追加
, state //既存state
, {count: Number(action.count) + 1} //別途変更がある箇所
)
);
default:
return state;
}
}
//state初期化
const initialState = {
count: 0
};
//Store定義(初期stateとReducerのセット)
export default createStore(reducer, initialState);
Presentatinal
import React, { Component } from 'react';
// プレゼンテーション層
export default class App extends Component {
//イベントハンドラ
eClick(event) {
//コンテナでdispatchする
this.props.goDispatch(event.target.value);
}
render() {
return (
<div>
{/* 押されるたびにカウントアップするボタン */}
<button value={this.props.count} onClick={this.eClick.bind(this)}>ClickMe</button>
<br />
{this.props.count}
</div>
);
}
}
Container
import { connect } from 'react-redux'
import App from './App.js';
//イベントをpropsで使うために定義
function addProps(dispatch) {
return {
//propsで参照できるイベントを定義
goDispatch(count) {
//stateが変更させたいので、イベント発火でReducerを起動させる
dispatch(actOne(count));
}
};
}
//Action作成
function actOne(count) {
return {
type: 'ACT1', //Reducerで、「action.type」で参照
count //Reducerで、「action.count」で参照
};
}
//state->props変換
function mapStateToProps(state) {
return {
count: state.count
};
}
//プレゼンテーション層に、変換ルールとイベントハンドラを紐付け
export default connect(
mapStateToProps
,addProps
)(App);