CTO室エンジニアの安達 (@dachi_023) です。React v16.8で待ちに待ったHooksが (20日前に) リリースされました。そんなHooksを使ってFluxやる時ってどうするんだろう?と思ったので調べて写経してみました。
書いたコード
create-react-app して出てきたものをいじって作りました。Flux化するまでに書いたコードだけを見たい場合は コミットログ が分かりやすいかもしれません。
余談: 実際書いてみて「Hooksちゃんと理解してなかったなぁ、9割くらい写経だとしても覚えるために書くというのは良いことだ」と感じられました。
やってみる
- お題: +1 / -1 ができるカウンターの実装
- useReducer() とContextでStoreをつくる
- Actionの発行 → dispatch → Stateの更新 → 再描画 をやる
Context
ContextがStateを管理するための入れ物 & 伝搬役になります。とりあえず createContext() して返します。今回はdispatchも同じContext内で管理します。
import { createContext } from 'react' export default createContext({ count: 0, dispatch: null })
Reducer
Actionを受け取って新しいStateを返します。Actionの中身は redux-utilities/flux-standard-action と同様です。Reducerの定義は Reactのドキュメント にも書いてありますが (state, action) => newState
です。
export default function CounterReducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } default: return state } }
ActionCreator
外部通信やビジネスロジックがないのでほぼ無意味な感じになってますが前述したflux-standard-actionに倣ってActionを生成して dispatch() します。
export function increment(dispatch) { dispatch({ type: 'increment', payload: {} }) } export function decrement(dispatch) { dispatch({ type: 'decrement', payload: {} }) }
Component
作ったものをつなげていきます。
App.js
useReducer() にCounterReducerとStateの初期値を渡してstateとdispatchを取り出しProviderに突っ込めばStateの生成と値の伝搬作業が完了します。
import React, { useReducer } from 'react' import Counter from './Counter' import CounterContext from './contexts/CounterContext' import CounterReducer from './reducers/CounterReducer' import './App.css' export default function App() { const [state, dispatch] = useReducer(CounterReducer, { count: 0 }) return ( <div className="App"> <header className="App-header"> <CounterContext.Provider value={{ ...state, dispatch }}> <Counter /> </CounterContext.Provider> </header> </div> ) }
Counter.js
import React, { useContext } from 'react' import CounterContext from './contexts/CounterContext' import { increment, decrement } from './actions/CounterActions' export default function Counter() { const { count, dispatch } = useContext(CounterContext) return ( <> <h2>{count}</h2> <button onClick={() => increment(dispatch)}>Increment</button> <button onClick={() => decrement(dispatch)}>Decrement</button> </> ) }
できあがったのがこれ
ちゃんと増えたり減ったりするようになりました!
まとめ
- 今までのClassベースのComponentとは書きっぷりがだいぶ異なるので最初とっつきづらいのかもなと思いました、1回でも真面目にやると「なんだ全然できるじゃん!」という感じですが。
- 複雑なことしないのであればFluxライブラリなど使わずにHooksで実装しちゃってもいいかもしれないですね。結構使えそうじゃないかな?と思ってます。