Nullable<T>

useReducerとuseContextを組み合わせる

February 09, 2019

useReducerとuseContext

useReduceruseContext(ContextAPI)を組み合わせてReduxライクな状態管理をする。

Action

const actionType = {
    increment: "increment",
    decrement: "decrement",
    reset: "reset"
}

Reducer

const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        case 'reset':
            return init(action.payload);
        default:
            return state;
    }
}

State and Dispatch

ContextAPIを利用して、StateDispatch用のContextを定義する。 それらのContext.ProvideruseReducerの戻り値( StateDispatch )を渡す。

それぞれのContextに対してコンポーネント内でuseContext呼び出すのはちょっとアレなので、明示的にuseXxxxConntextというメソッドも用意する

const CounterStateContext = createContext();

const useCounterStateContext = () => {
    return useContext(CounterStateContext);
}

const CounterDispatchContext = createContext();

const useCounterDispatchContext = () => {
    return useContext(CounterDispatchContext);
} 

const CounterProvider = ({ children }) => {
    const [state, dispatch] = useReducer(reducer, initialCount, init);
    return (
        <CounterStateContext.Provider value={state}>
            <CounterDispatchContext.Provider value={dispatch}>
                {children}
            </CounterDispatchContext.Provider>
        </CounterStateContext.Provider>
    )
}

Component

useXxxxContextメソッドを利用して、statedispatchを使う。

const Counter = () => {
    const state = useCounterStateContext();
    const dispatch = useCounterDispatchContext();
    return (
        <div>
            Count: {state.count}
            <button onClick={() => dispatch({ type: actionType.reset, payload: initialCount })}>
                Reset
            </button>
            <button onClick={() => dispatch({ type: actionType.increment })}>+</button>
            <button onClick={() => dispatch({ type: actionType.decrement })}>-</button>
        </div>
    )
}

View

const UseReducerWithContextView = () => {
    return (
        <div>
            <h1>useReducerWithContext</h1>
            <CounterProvider>
                <Counter />
            </CounterProvider>
        </div>
    );
}

Utils

const initialCount = 1;

const init = () => {
    return { count: initialCount };
}

おわりに

Store周りのコードを書かないため、ミドルウェアを入れられない。 やはり、大規模であったりミドルウェアをぶちこみたいなら素直にRedux入れたほうが良さそう。

ただ、軽い状態管理であれば、ライブラリは入れずともReactが提供する機能群で事足りそうだ。

ソースはGitHubに上げている。


Written by Ryo @neer_chan

© 2018-2020 Nullable<T>