Reduxが何をやっているのかがわからない。
React + Reduxではない。
Redux単体だ。
Reduxについて調べるとやれReactとの連携とか、
Fluxについて高尚な演説が永遠と続く。
で、結局Reduxって何やってるの?と無限ループしてしまう。
そこでRedux以外は本当に何も使わずに簡単なアプリを作ってみる。
#どういったアプリを作るか
やみくもに作ってもしょうがないので仕様を決める。
- フォームに画像のURLを入力してSetボタンを押すとURL情報を内部に保存する
- Getボタンを押すと内部に保存されたURLの画像を表示する
Reduxの特性を活かすべく、
内部に状態を持ってビューの出し分けをすることを考える。
#HTML
まず以下のようなHTMLを用意する。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>title</title>
</head>
<body>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
<script src="main.js"></script>
</html>
<body>タグは漢らしく空で、
CDNからreduxのjsをロードして処理の本体はmain.jsに書くという構造だ。
Reactとかnpm installとかはカケラも出てきてない。
なお以降の操作はChromeで行ってもらいたい。
ES6のコードがある程度そのまま動くので。
#初期化
// 初期化
(() => {
document.body.innerHTML = `
<button onclick="actionSetURL(document.getElementById('url').value)">Set</button>
<button onclick="actionGetImg()">Get</button>
<button onclick="actionClear()">Clear</button>
<input id="url" type="text" style="width: 600px;" />
<div id="render"></div>
`;
})();
bodyに骨格となるHTMLを直接流し込んでいるだけです。
ボタンや入力フォームの作成を行っています。
actionSetURL()ではフォームに入力された値を引数として渡していて、
actionGetImg()、actionClear()は引数なしということがわかるかと思います。
続いてこの3つの関数を書いていきます。
#Action
Actionとは後述するStoreを変更するための機構である。
typeは必須で他の引数は任意である。
typeは全て大文字のスネークケースで書くのが習わしらしい。
Storeを更新したいときはActionを発行するのが基本パターンだ。
// Action
const actionSetURL = url => url && store.dispatch({ type: 'SET_URL', url });
const actionGetImg = () => store.dispatch({ type: 'GET_IMG' });
const actionClear = () => store.dispatch({ type: 'CLEAR' });
#Reducer
ReducerとはActionと現在のStateを受け取って、
新しいStateを返す関数である。
// Reducer
const reducerURL = (state = '', action) => {
switch (action.type) {
case 'SET_URL':
return action.url;
case 'CLEAR':
return '';
default:
return state;
}
}
const reducerImg = (state = false, action) => {
switch (action.type) {
case 'GET_IMG':
return true;
case 'CLEAR':
return false;
default:
return state;
}
}
この例だとreducerURLはstringを返していて
action.typeがSET_URLのときはフォームに入力した値を返し、
action.typeがCLEARのときは空文字を返す。
reducerImgはbool変数を返している。
Reducerをまとめる
const combineReducers = reducers => (state = {}, action) => Object.keys(reducers).reduce((nextState, key) => {
nextState[key] = reducers[key](state[key],action);
return nextState;
}, {});
const rootReducer = combineReducers({ reducerURL, reducerImg });
先の例ぐらいだと1つのReducerに書いてもいいかもしれないが、
規模が大きくなると分割する必要が出てくる。
複数のReducerをまとめるcombineReducersを書いて
1つに集約したものをrootReducerとする。
#Store、Provider、Render
StoreとはReducerで変更されたstateを保存する場所である。
Redux.createStore()にReducerを入れて作成する。
providerはstateを受け取ってHTMLを構築する関数だ。
stateはReducer経由でしか更新されない読み取り専用オブジェクトだ。
読み取りにはstoreに対してgetState()を呼べばいい。
renderはproviderで構築されたHTMLを描写する関数だ。
// Store, Provider, Render
const store = Redux.createStore(rootReducer); // storeの作成
const provider = state => {
const stateView = `
<div>
<div>state.reducerURL: ${state.reducerURL}</div>
<div>state.reducerImg: ${state.reducerImg}</div>
</div>
`;
const imgView = state.reducerImg ? `<img src='${state.reducerURL}' />` : '';
return `
<div>
${stateView}
${imgView}
</div>
`;
}
const render = () => { document.getElementById('render').innerHTML = provider(store.getState()); }
store.subscribe(render);
render();
#まとめ
以上でアプリケーションは動作する。
actionからstateが変更されるまでの流れがよくわかった気がする。
providerあたりでやっているHTMLの構築をスッキリ書けるのがReactということなのだな。