React Nativeなんかをやりだすと、きっと出てくるであろうRedux。
今日はそんなReduxについてまなんでみよう。
とても簡単に。超シンプルに。
とくに分かりやすいなーと感じた手法の1つが
Reduxの項目である
Action、Reducer、Store
について1枚のJSファイルのなかに書いてすすめてくってのがありました。
今回この手法で進めてみようと思います。
セットアップはCreate-react-appを使ったものです。楽やし。
まずはReduxとはなんだろうか。
公式にはこうあるね。
Redux is a predictable state container for JavaScript apps. (Not to be confused with a WordPress framework – Redux Framework.) It helps you write applications that behave consistently, ... ... You can use Redux together with React, or with any other view library.
状態管理がしやすくて一貫性のあるコーディングをサポートするよ。
ただWordPressのフレームワークとは違うからな。
みたいな感じでしょうか。
Reactとの使用例が多いけどとくにReactに依存したものでもないらしい。
たぶんReactでは、react-reduxというのとreduxというライブラリを使ってるのではと思います。
んで、今回はreduxだけを使った感じで進めていきます。
create-react-appで生成したindex.jsを使って進めます。
$ create-react-app testredux $ cd testredux $ npm i redux
まずは状態を作ります。JavascriptとKotlinというタスクを持ったアナキン君という設定で。
const initialState = { tasks: ['Javascript', 'Kotlin'], name: "アナキン", darkside: false };
つづいてReducerをつくります。Reducerとは"Storeが保持してる状態を変化させる関数。"とのこと。
Actionから送られてきた指示を返すかんじでしょうか。
function addReducer(state = initialState, action) { switch (action.type) { 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; } }
Storeを定義します。
アプリ全体の状態とロジックをStoreで集約管理します。
const store = createStore(addReducer);
ActionCreatorの定義をします。
Actionを生成するための関数で、Actionについては、ユーザからの入力とかAPIからの情報取得とかなんらかの状態変化を引き起こす現象を指します。
const changeName = (name) => ({ type: 'CHANGE_NAME', payload: { name } }); const addTask = (task) => ({ type: 'ADD_TASK', payload: { task } }); const tDS = () => ({ type: 'TO_DARKSIDE', });
これで準備できました。
今回はReactは使ってないのでConsole.logで状態を見る感じですすめます。
今は起こせるアクションを3つもっていて、Storeはアナキンの状態を持っていますね。
アクションを起こすときはStoreのDispatchメソッドを使います。
addTaskでPerlのタスクを足します。
store.dispatch(addTask('Perl')); console.log(store.getState());
Chromeとかのデベロッパツールのコンソール画面を見るとこうなってますね。
つづいてtDSのアクションを起こします。
store.dispatch(tDS()); console.log(store.getState());
アナキンは暗黒面に落ちてしまいました。
ではベイダーと名付けましょう。
store.dispatch(changeName('ベイダー')); console.log(store.getState());
※Perlは黒魔術師が使うというところから引用してます。
さっきも書いたけど、今回はReactを使ってない(=Viewがない)んでActionをそのまま叩いてますが、実際はユーザが画面からなんらかのアクションを発生させますよね。
ということは、また、この結果が画面に戻って行くことをイメージすると、View > ActionCreator(Action) > store:{ Reducer > State } > View とぐるぐる回ってるイメージになります。ならない?たぶんなる。こういう概念をFluxというらしいです。
さっきはConsole.Logを使いましたが、Storeのメソッドにsubscribeというのがあり、Storeの状態が変更されたときに呼び出す関数を作れます。
こうします。
function handleChange() { console.log(store.getState()); } const unsubscribe = store.subscribe(handleChange);
まるっと書き換えると
function handleChange() { console.log(store.getState()); } const unsubscribe = store.subscribe(handleChange); store.dispatch(addTask('Perl')); store.dispatch(tDS()); store.dispatch(changeName('ベイダー')); unsubscribe();
dispatchが3回はしってて、3回うごいてます。
CombineReducer
Reducer1つではもちろんやっていくのはしんどいので、いくつかあるReducerを合成するReduxがもってるメソッドです。
ってことで、この辺がだいたいできたらReact-reduxとまぜて、あとファイル構成も分割してやれば理解が深まるんじゃないかとと思います。
以下のはコード全部です。
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; import { createStore } from 'redux'; const initialState = { tasks: ['Javascript', 'Kotlin'], name: "アナキン", darkside: false }; /** * Reducer * Storeが保持してる状態を変化させる関数。 * * @param {*} state * @param {*} action */ function addReducer(state = initialState, action) { switch (action.type) { 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; } } /** * Store * アプリ全体の状態とロジックをStoreで集約管理する。 */ const store = createStore(addReducer); /** * ActionCreator * Actionを生成するための関数。 * * Action * ユーザからの入力とかAPIからの情報取得とか * なんらかの状態変化を引き起こす現象を指す。 * */ const changeName = (name) => ({ type: 'CHANGE_NAME', payload: { name } }); const addTask = (task) => ({ type: 'ADD_TASK', payload: { task } }); const tDS = () => ({ type: 'TO_DARKSIDE', }); function handleChange() { console.log(store.getState()); } const unsubscribe = store.subscribe(handleChange); store.dispatch(addTask('Perl')); store.dispatch(tDS()); store.dispatch(changeName('ベイダー')); unsubscribe(); ReactDOM.render(<App />, document.getElementById('root')); registerServiceWorker();