【React】stateに変更がない場合のレンダリング
概要
Reactのstate
を使用している時に、state
の値が変わっていないはずなのに再レンダリングされていることに気づいたので、調査してまとめました。
コード例
以下のように、例としてstate
に0
か1
の値を加算していくコードについて考えます。
レンダリングのタイミングをログで出しています。codeSandBoxも貼ってるので、再レンダリングのタイミングを思い出しながら、ボタンをクリックして遊んでみてください。
const Component = () => {
console.log("render");
const [state, setState] = useState(0);
const data = [0, 1];
return (
<>
{data.map((datum) => (
<button onClick={() => setState((state) => state + datum)}>
+ {datum}
</button>
))}
<p>state: {state}</p>
</>
);
};
再レンダリングが起こるタイミング
codeSandBoxで試してもらえば分かりますが、レンダリング(consoleにrender
が表示される)は以下のタイミングだと分かります。
- 一番初めのレンダリング
-
+1
をクリックしてstateの値が増えた時 -
+1
をクリックしたあとに+0
をクリックした時
1.2. については、Reactの中では当たり前といえるレンダリングの原因だと思います。
3.についてはどうでしょうか。+1
をクリックするとstate
の値が変わるため再レンダリングされるのは分かります。その後に+0
をクリックしてもstate
の値は0
が足されるだけなので、結局state
の値が変わらず再レンダリングは起こらないはずです。
しかし、今回の場合は再レンダリングが発生しています。
原因
Reactのissueにて訪ねてみると、以下のような返信が届きました。
when you first click "unchanged state" the last reducer rendered was setState(state => state +1), so when we compare it to the result of setState(state => state + 0), they're different . When you click again, the last reducer was setState(state => state + 0), so they match and we can bail out.
[Bug: Parent rerenders unnecessarily on Child-to-Parent setter call, while child rerenders only when changes occur. · Issue #28287 · facebook/react](https://github.com/facebook/react/issues/28287#:~:text=when you first,closed over props.
まず、前提としてstate
のset関数
が呼び出されると、そのコンポーネントは再レンダリングが予約されます。その時にもし、set関数
のコールバック関数に変更があった場合は何もせず、再レンダリングを実行しますが、set関数
のコールバック関数に変更が無かった場合は予約を解除することによって、再レンダリングを実行しない仕組みになっています。
ただし、state
の値が同じであっても最初の1回目のみは、set関数
中のコールバック関数で使われているprops
やstate
、hook
等の変更が反映されていないときがあり、本当に同じ値かを確かめるために、もう一度レンダリングし、hook
等を再実行した上で同じ値である事を確認します。2回目以降でこのset関数
のみが呼び出された場合は、他のprops
やstate
等は変更されていないはずなので確認のレンダリングは必要なくなります。
今回の例でいうと、+1
ボタンをクリックするとsetState(state => state + 1)
のコールバック関数の返り値が1
になり、state
の値は1
になります。次に+0
をクリックすると、setState(state => state + 0)
のコールバック関数の返り値は1
になり、同じ値が続くのが1回目なので確認のためにレンダリングが走り、consoleにrender
が表示されます。もう一度+0
をクリックすると、コールバック関数の返り値は1
になり、同じ値が2回続き、このset関数
のみ呼び出していて他のprops
やstate
等に変更はないはずなのでレンダリングが走らず、consoleにrender
が表示されません。
最後に
実際、値が変わらないset関数
を使用することや、レンダリングのタイミングによってバグが発生する事は少ないかもしれませんが、知っておいて損は無い知識だと思います。周りに自慢しましょう!💪
今回の調査で様々なことが分かりましたが、set関数
でstate
に値が入るタイミングやReactの再レンダリングの予約から完了までのフェーズがどうなっているか等は実装を読まない限り分かりませんでした。
もし、気になったらReactの実装等を読んでコメントとかで詳しく教えてください。
Discussion