はじめに
React Contextでglobal stateを実現する際、useStateやuseReducerを使ってcontextの伝搬で更新するやり方が知られていますが、contextを一つだけにすると、無駄なレンダリングが生じることがあります。そこで、contextの伝搬を使う場合は、contextを細かく分けることがおすすめです。
コード
最初にuseReducerでstateを作るカスタムフックを作ります。
const useValue = () => useReducer((state, action) => {
if (action.type === 'INC_COUNT1') {
return { ...state, count1: state.count1 + 1 };
}
if (action.type === 'INC_COUNT2') {
return { ...state, count2: state.count2 + 1 };
}
if (action.type === 'INC_BOTH_COUNTS') {
return { ...state, count1: state.count1 + 1, count2: state.count2 + 1 };
}
throw new Error('unknown action type: ' + action.type)
}, { count1: 0, count2: 0 })
次にそれぞれの項目を伝搬させるためのcontextを作ります。
const DispatchContext = createContext(() => {});
const Count1Context = createContext(0);
const Count2Context = createContext(0);
Providerコンポーネントは次のようになります。入れ子にならないようにreduceRightを使います。
const Provider = ({ children }) => {
const [{ count1, count2 }, dispatch] = useValue();
return [
[DispatchContext.Provider, { value: dispatch }],
[Count1Context.Provider, { value: count1 }],
[Count2Context.Provider, { value: count2 }],
].reduceRight(
(a, c) => createElement(c[0], c[1], a),
children
);
};
実際にstateを使う2つのコンポーネントがこちらです。2つともほぼ同じです。
const Counter1 = () => {
const count1 = useContext(Count1Context);
const dispatch = useContext(DispatchContext);
const inc = () => dispatch({ type: 'INC_COUNT1' });
return (
<div>
Count1: {count1} <button onClick={inc}>+1</button>
</div>
);
};
const Counter2 = () => {
const count2 = useContext(Count2Context);
const dispatch = useContext(DispatchContext);
const inc = () => dispatch({ type: 'INC_COUNT2' });
return (
<div>
Count2: {count2} <button onClick={inc}>+1</button>
</div>
);
};
最後に全体をまとめるコンポーネントです。
const App = () => (
<Provider>
<div>
<Counter1 />
<Counter1 />
<Counter2 />
<Counter2 />
</div>
</Provider>
);
CodeSandbox
https://codesandbox.io/s/usereducer-usecontext-6okyp
課題
今回はシンプルな例でしたが、オブジェクトの構造が複雑でも、必要な数だけcontextを作ればなんとかなります。しかし、contextは動的には作れない(作ることはできるが、全部再マウントすることになってしまう)ので、あらかじめ分かっている固定数のものにしか対応できません。
おわりに
これをすんなり使おうと思える方はぜひどうぞ。こんなことするくらいならライブラリを使おうと思う方は、これとかこれとかこれとかをどうぞ。