コネヒト開発者ブログ

コネヒト開発者ブログ

React HooksとContextAPIでFluxをやってみる

f:id:dachi023:20190226162905p:plain

CTO室エンジニアの安達 (@dachi_023) です。React v16.8で待ちに待ったHooksが (20日前に) リリースされました。そんなHooksを使ってFluxやる時ってどうするんだろう?と思ったので調べて写経してみました。

reactjs.org

書いたコード

create-react-app して出てきたものをいじって作りました。Flux化するまでに書いたコードだけを見たい場合は コミットログ が分かりやすいかもしれません。

github.com

余談: 実際書いてみて「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

の処理でProviderにセットしたStateを useContext() で取り出し表示します。Stateと一緒に入れてあるdispatchを取り出してクリックイベントに仕込めばStateの更新処理までが完了です。

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>
    </>
  )
}

できあがったのがこれ

ちゃんと増えたり減ったりするようになりました!

f:id:dachi023:20190226160647g:plain

まとめ

  • 今までのClassベースのComponentとは書きっぷりがだいぶ異なるので最初とっつきづらいのかもなと思いました、1回でも真面目にやると「なんだ全然できるじゃん!」という感じですが。
  • 複雑なことしないのであればFluxライブラリなど使わずにHooksで実装しちゃってもいいかもしれないですね。結構使えそうじゃないかな?と思ってます。

参考記事