はじめに
React HooksのuseStateとuseReducerに関する小ネタです。
useStateはuseReducerで実装されている
内部実装ではuseStateはuseReducerで実装されていると、どこかに書いてありました。こちらのブログ記事にuserlandでの実装例が載っています。
const stateReducer = (prevState, newState) =>
typeof newState === 'function' ? newState(prevState) : newState;
const stateInitializer = initialValue =>
typeof initialValue === 'function' ? initialValue() : initialValue;
const useState = initialValue =>
useReducer(stateReducer, initialValue, stateInitializer);
こんな感じになります。
つまり、useStateでできることは全てuseReducerでもできるということです。では、useReducerだけでしかできないことはあるのでしょうか。
useReducerはuseStateでも実装できる
実は、useReducerはuseStateでも実装できます。こちらのブログ記事にuserlandでの実装例があります。
const useReducer = (reducer, initialArg, init) => {
const [state, setState] = useState(
init ? () => init(initialArg) : initialArg,
);
const dispatch = useCallback(
action => setState(prev => reducer(prev, action)),
[reducer],
);
return useMemo(() => [state, dispatch], [state, dispatch]);
};
こんな感じになります。つまり、機能的には同等ということになります。
実はわずかな違いがある
I just noticed, if we were to use lazy initialization using props with useState, it would be better to do it with useReducer which has initArg, because that could allow #JavaScript engine to optimize the inline function without closure.#ReactJS #React #ReactHooks pic.twitter.com/M0VsdhLXNd
— Daishi Kato (@dai_shi) 2019年11月13日
こちらのツイートにあるように、useReducerではinitArgとinitが分離されているため、initをpropsに依存しないように書くことができます。これにより、hookの外側で関数定義することもできますし、inline関数で書いたとしてもJavaScriptのランタイムエンジンで最適化される可能性が高いです。
決定的な違いもあるにはある
お勧めしませんが、useReducerには非常に特殊な利用法もあります。それはreducerをコンポーネント内で定義できることです。reducerをコンポーネント内で定義すると未来のpropsを読み込めちゃいます。"cheat mode"と呼ばれたりします。
詳細は、こちらのブログ記事のセクションをご参照ください。
追記(12/4)
このcheat modeは上記のuserland実装では再現できていないですね。
おわりに
小ネタにしようかと思っていましたが、書き始めたらマニアックなネタになってしまいました。楽しんでくれた方がいらしたら幸いです。