概要
mizchiさんの実践:React HooksなどにあるようにuseReducer
とuseContext
をつかうことでReduxなしでReduxみたいなことができます。
小さな個人開発ではよく使えそうなテクニックだと思ったので、快適にそれを行うためのライブラリ、reducer-context-hookを勉強がてら作ってみました。
APIはfacebookincubator/redux-react-hookをパクり参考にしました。
リポジトリ: https://github.com/sosukesuzuki/reducer-context-hook
API
create()
exportされているのはcreate
という関数だけです。StoreContextProvidr
、useDispatch
、useMappedState
を持つオブジェクトを返す関数です。引数はありませんが、型引数としてStateとActionの型を渡します(デフォルトだとany)。返り値のオブジェクトに含まれる3つをそのままexportせずにcreate
をexportさせたのは単純に型引数を渡したかったという理由です。
import create from "reducer-context-hook";
const { StoreContextProvider, useDispatch, useMappedState } = create<
State,
Action
>();
StoreContextProvider
create
の返り値のうちの1つです。propsとしてreducerと初期ステートを受け取ります。内部的にはそのままContextオブジェクトのProviderになっているのでコンポーネントツリーのルートあたりで囲んであげましょう。
function reducer(state: State, action: Action): State {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
default:
throw new Error();
}
}
const initialState: State = {
count: 0
};
function App() {
return (
<StoreContextProvider reducer={reducer} initialState={initialState}>
<App />
</StoreContextProvider>
);
}
useDispatch()
とてもシンプルなHookで、dispatch
を返します。Actionを作って使いましょう。あと、Reduxを使わないといいましたが、ReduxのbindActionCreatorsはここで使えますね。(同様にcombineReducersもreducer定義で使えそう)
function Increment() {
const dispatch = useDispatch();
const increment = React.useCallback(
() => dispatch({ type: "increment" }),
[]
);
return <button onClick={increment}>+</button>;
}
useMappedState(mapState, memoizationArray)
react-reduxで言うところのconnect(State限定)にあたります。Stateを引数にとってほしい値を返す関数(mapState関数)と、メモ化のためのキーを配列として引数にとります。内部ではuseCallback
を使ってメモ化しています。
function Counter() {
const { count } = useMappedState(
state => ({
count: state.count
}),
[]
);
return <p>{count}</p>;
}
感想
あ、まだnpmにpublishしてません。テスト書き終わったらpublishします。
Custom Hooksを書くのってとても楽しいです。これからも色々書いていきたいと思います。