利用プロジェクト
Flux
Reactだけだと、データが大規模になりソースが肥大化してしまう欠点があります。
それらを解消するため、Facebookが編み出したデザインパターンをFluxと言います。
今回はそのFluxフレームワークの一つであるReduxを主軸に
規模が大きくなりデータの管理が難しくなったReactのプロジェクトに
Reduxを導入するところを記載します。
Reduxデータフロー
Action(アクション)
Viewから呼ばれるStateに関する関数群
それぞれ、該当するReducerを呼ぶ。
Reducer(レジューサ)
Actionを受け取り、それに従って書き換えるStateの中身を作る。
ここでは、Stateは書き換えない。
Dispatch(ディスパッチ)
Actionを呼ぶ際に利用され、Dispatchを通すことでStateの更新が可能になります。
Store(ストア)
ActionとReducerを結びつける。dispatchを通してReducerから受け取ったStateのコピーの内容でStateを書き換える。
Container(コンテナ)
Storeが持つStateをReactのプロパティとしてViewと結びつける役割を持つ。
View(ビュー)
Reactのコンポーネントの部分、Stateを表示する。
Reduxの導入手順
1. ライブラリをインストールする。
必要なものは、以下の2点です。
- Redux
Redux本体 - React-Redux
ReactとReduxを結びつけるライブラリ
コマンドでインストールします。
# Redux
# npm
npm install redux
# yarn
yarn add redux
# React-Redux
# npm
npm install react-redux
# yarn
yarn add react-redux
2. ReactとReduxを結びつける。
まず、最初の手順としてReactとReduxを結びつけます。
記述するのはindex.jsxです。
// ./index.jsx
import React from 'react'
import ReactDOM from 'react-Fdom'
// ①
import { createStore } from 'redux'
import { Provider } from 'react-redux'
// ② 4の項目で解説
import App from './containers/App'
// ③ 次の項目で解説
import reducer from './reducers/reducer'
// ④
const store = createStore(reducer)
// ⑤
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
①. ReduxのcreateStore, React-ReduxのProviderをインポートします。
②. 4の項目で解説しますが、作成したAppのContainerをインポートします。
③. 次の項目で解説しますが、作成したReducerをインポートします。
④. reducerを元に、ReduxのcreateStoreでStoreを作成します。
⑤. ProviderにstoreをPropsとして渡し,App(Component)とStoreを結びつけます。
3. Reducerを作成する。
次に、書き換えるStateの中身を作成するReducerを作成します。
// ./reducers/reducer.jsx
// 初期State
const initialState = {
tasks: [],
text: '',
}
// Reducer処理
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADDTASK': {
return console.log(action.value)
}
case 'ENDTASK': {
return console.log(action.value)
}
case 'UPDTASK': {
return console.log(action.value)
}
case 'DELTASK': {
return console.log(action.value)
}
default: {
return state
}
}
}
export default reducer
Reducerの最初はStateの初期値です。
今回のReact-Todoでは、
- ADD: Todoの追加
- END: Todoの完了
- UPD: Todoの更新
- DEL: Todoの削除
以上、4つのState変更があります。
ReducerはActionから「ActionType」というものを引数で受け取り、その中身でSwitchケースの中の該当する処理を行います。
今はまだ処理を書かず、return nullとしていますが、
例えばADDTASKでは、現状のStateの追加するTodoを追加する処理を記述します。
(なんとも説明が下手なのですが....)
ざっくりいうと、State自体は書き換えずにStateを更新した際の値をreturnする処理を記述します。
この項で作成したreducerを前の項のreducerとしてimportしましょう。
4. Containerを作成する。
ReduxのStoreが持つStateをReactのコンポーネントが利用できるようにするため、Containerを作成します。
// ./containers/App.jsx
import {
connect,
} from 'react-redux'
import App from './../components/App'
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps)(App)
React-Reduxは、connectというプロパティを持っています。
mapStateToPropsは、全体的なStateの中から利用するStateを持ってきてくれる関数です。
正直、中でどう動いているのか、どういう意味なのかははっきりは僕もわかりません。
なので、これはお作法だ、と考えています。
最後の行、connectを利用して、mapStateToPropsが取ってきたStateの値をAppで利用できるように繋げています。
containerに関しては、ほとんどこれだけです。
大規模になればなるほど、mapStateToPropsで利用するStateが多くなります。
5. Actionを作成する。
Actionsには、Reducerへ渡すActionTypeとStateへ追加、または更新する値を渡します。
// ./actions/AppActions.jsx
const Actions = {
addTodo(value) {
return {
type: 'ADDTASK',
value,
}
},
fixTodo(value) {
return {
type: 'FIXTASK',
value,
}
},
updTodo(value) {
return {
type: 'UPDTASK',
value,
}
},
delTodo(value) {
return {
type: 'DELTASK',
value,
}
},
}
export default Actions
各関数の中でreturnすることで、Reducerへ引数が渡されます。(returnの値)
それぞれReducerの中でaction.type , action.valueで取得できます。
addTodoであれば、typeがADDTASKなので
Reducer内のADDTASKの処理を行います。
valueは、Componentから渡される値です。
Actionに記載している関数一つ一つをActionCreatorと呼びます。
Reduxのデータフロー
以上でReduxの導入で必要なファイル群の作成は完了しました。
続いて実際に、Actionを読んで、Reducerが値を作成し、StoreがStateを書き換えるという流れを記載、もとい現状の処理をReduxへ書き換える処理を記述していきます。
1. ActionCreatorを呼ぶ
ReactのViewからReducerを呼ぶにはdispatchが必要です。
そのため、Containerに以下の記述を行います。
// ./containers/App.jsx
import {
connect,
} from 'react-redux'
import App from './../components/App'
import Actions from './../actions/AppActions'
const mapStateToProps = (state) => {
return state
}
const mapDispatchToProps = (dispatch) => {
return {
handleTodoAdd(value) {
dispatch(Actions.addTodo(value))
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
ActionsのaddTodoをインポートし、mapDispatchToPropsの中で
handleTodoAddの関数内でdispatchを用いてaddTodoを呼ぶようにします。
それらをconnectを利用してAppへPropsを渡します。
以後、ActionCreatorは同じような形で記述して行きます。
作成したActionCreatorは、以下の記述でReact側から呼び出しが可能です。
// ./components/App.jsx
...
AddTasks() {
if (!(this.state.text)) {
return
}
this.props.handleTodoAdd(this.makeTasks(this.state.text))
}
...
タスクを追加する部分です(makeTasksというのは、タスクにidをつけたりする関数です。)
containerからPropsとしてStateとActionCreatorが渡されるため、this.propsでアクセスします。
this.stateというのは、基本的に面倒なhandleChangeで変化するStateです(個人的にhandleChangeといったものはRedux側で管理したくないので、残しています。)
2. Reducerの記述
ActionCreatorの作成、呼び出しは完了しました。
それでは実際に、書き換えるStateを作成するReducerを記述します。
// ./reducers/reducer.jsx
const initialState = {
tasks: [],
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADDTASK': {
return {
tasks: state.tasks.concat(action.value),
}
}
case 'ENDTASK': {
return {
tasks: action.value,
}
}
case 'UPDTASK': {
return {
tasks: action.value,
}
}
case 'DELTASK': {
return {
tasks: action.value,
}
}
default: {
return state
}
}
}
export default reducer
まだ、ADDTASKの部分だけですが、stateが現在のStateになります。
それに、concat(結合)を利用することで現在のStateを更新することなく、Storeへタスクを追加した値を渡すため書き換えることが可能です。
オブジェクトの場合はObject.Assignとかを利用します。
これで、React側でthi.propsよりhandleTodoAddを呼び出すことで、ActionCreatorのaddTodoが呼び出されStateのTasksに新しいTodoが追加されます。
React-Todo Redux版
下記のリポジトリでご確認ください。
React-Todo Redux版
後書き
Reactはその特性上、いろんなことができそうですが
大規模な案件、子コンポーネントが増えてしまうとそれだけStateが増えてしまいReduxの導入が必須になってきます。
導入してみれば意外と簡単で、何より、ActionCreatorを呼ぶことでいわば、どこからでもStateの更新が可能なのでとても便利です。
小規模な場合は問題ないかもしれませんが、大規模なプロジェクトでは導入をオススメします。