前回超シンプルにReduxをまなぶの続き。
できるかぎり簡単になるようにReduxとReact-Reduxを別々に覚えようというもので
今回はReact-Reduxです。
準備は
$ npm i react-redux
Container Comopnent と Presentational Component
Container Component
- ReactとReduxの橋渡し。ReduxのStoreやActionを受け取ってReactコンポーネントのPropsとして渡す。
Presentational Component
- Redux依存のない純粋なReactコンポーネント。
前回作ったアプリにReact-Reduxを導入してみる。
前回つくったReduxのサンプルに、画面から操作できるようにReactコンポーネントを1つ付け足してアプリにして、React-Reduxを導入してみる。
重要なとこは Provider Connect の2つ。
ここでちょっと復習で、アクションを起こすDispatchはStoreのメソッドでした。
なので、けっこう深いとこにDispatchしたコンポーネントがあったとすると、そこまでのコンポーネントにたいしてバケツリレーさせる必要が出てくる。
そこでReact-ReduxのProviderとConnectを使うとどうなるかと、最上位のコンポーネントをProviderでラップして、Connectが特定のReactコンポーネントに対して、保持しているStoreを提供する。各コンポーネントにStoreを与えることができる感じで、このニコイチで動作するっぽいですが、これがなかなかイメージ掴みにくいんだよ。
一方通行なんですって言っちゃうとすごく簡単に聞こえるんやけど。
言葉の説明はこの辺で、あとはコードリーディングの方が掴めそうな気がするのでコードで。
できあがりはこんな感じです、今回もPerlタスクを得たときにダークサイドになるというもの。
前回から増えたのはComponentとContainerの2つ。
出来上がったディレクトリ構成はこんなの。
src/ actions/ components/ containers/ reducers/ index.js
.../src/actions/tasks.js /** * ActionCreator * Actionを生成するための関数。 * * Action * ユーザからの入力とかAPIからの情報取得とか * なんらかの状態変化を引き起こす現象を指す。 * */ export const changeName = (name) => ({ type: 'CHANGE_NAME', payload: { name } }); export const addTask = (task) => ({ type: 'ADD_TASK', payload: { task, } }); export const tDS = () => ({ type: 'TO_DARKSIDE', }); export const inputTask = (task) => ({ type: 'INPUT_TASK', payload: { task } });
.../reducers/tasks.js const initialState = { tasks: ['Javascript', 'Kotlin'], name: "アナキン", darkside: false, task: '' }; /** * Reducer * Storeが保持してる状態を変化させる関数。 * */ export default function addReducer(state = initialState, action) { switch (action.type) { case 'INPUT_TASK': return { ...state, task: action.payload.task }; case 'CHANGE_NAME': return { ...state, name: action.payload.name }; case 'ADD_TASK': return { ...state, tasks: state.tasks.concat([action.payload.task]) }; case 'TO_DARKSIDE': return { ...state, darkside: true }; default: return state; } }
.../containers/PerlWars.js import { connect } from 'react-redux'; import PerlWars from '../components/PerlWars'; import { changeName, addTask, tDS, inputTask } from '../actions/tasks'; /** * mapStateToProps Storeから必要なStateを取り出してComponentのPropsに * 割り当てる関数を指定。 */ function mapStateToProps({ tasks, name, darkside, task }) { return { tasks, name, darkside, task, }; } /** * Dispatchの処理を閉じることでDispatchの概念を閉じ込める。 */ function mapDispatchProps(dispatch) { return { inputTask(task) { dispatch(inputTask(task)); }, addTask(task) { if (task !== undefined) { dispatch(addTask(task)); } }, tDS() { dispatch(tDS()); }, changeName(name) { dispatch(changeName(name)); }, }; } export default connect(mapStateToProps, mapDispatchProps)(PerlWars);
.../components/PerlWars import React from 'react'; import Reboot from 'material-ui/Reboot'; import Button from 'material-ui/Button'; import TextField from 'material-ui/TextField'; import Input from 'material-ui/Input'; export default function PerlWars({ inputTask, addTask, tDS, changeName, tasks, name, darkside, task }) { let give = (n) => { if (n === 'perl' || n === 'Perl' || n === 'PERL') { tDS(); changeName('ベイダー卿'); return n; } else if (n !== '') { return n; } else { return undefined; } } return ( <div style={{ margin: 20 }}> <Reboot /> <div> Name: {name} Force: {darkside ? 'シス' : 'ジェダイ'} </div> <TextField style={{ margin: 5 }} refs="textbox" onBlur={(e) => e.target.value = ''} onChange={(e) => inputTask(e.target.value)} /> <Button raised color="primary" onClick={() => addTask(give(task))}> 大いなる力 </Button> <ul> { tasks.map(function (item, i) { return ( <li key={i}>{item}</li> ); }) } </ul> </div > ); }
.../index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import addReducer from './reducers/tasks'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import PerlWars from './containers/PerlWars'; /** * Store * アプリ全体の状態とロジックをStoreで集約管理する。 */ const store = createStore(addReducer); ReactDOM.render( <Provider store={store}> <PerlWars /> </Provider>, document.getElementById('root') );
ぼちぼちReactアプリ作成の基盤ができる目処がたってきたので、次回はまともなアプリをつくるぞー!