再レンダリングの仕組み
3 つの段階
ボタンを押してから画面が変わるまで、React は毎回同じ 3 つの段階を通ります。
- きっかけ ... 更新関数が呼ばれ、React に「state が変わった」と伝わる
- レンダリング ... React がそのコンポーネント関数をもう一度呼び、返ってきた JSX で新しい画面の設計図を作る
- 反映 ... 前の設計図と見くらべ、違うところだけを実際の画面に書き込む
大事なのは 2 番です。React は画面を部分的に手直ししているのではなく、関数をまるごと呼び直して、新しい結果と前の結果を見くらべて います。
呼び直されているのを目で見る
コンポーネントの中に console.log を置くと、いつ呼ばれているかが分かります。
import { useState } from "react";
export default function App() {
const [likes, setLikes] = useState(0);
console.log("App がレンダリングされました", likes);
return <button onClick={() => setLikes(likes + 1)}>いいね {likes}</button>;
}押すたびにログが出ます。関数の先頭から毎回やり直しているので、const [likes, setLikes] = useState(0) の行も毎回通っています。それでも 0 に戻らないのは、値を覚えているのが React 側だからです。
反映されるのは違うところだけ
3 番のおかげで、画面全体が作り直されるわけではありません。見出しの文字が変わっていなければ、その部分の DOM はそのまま残ります。だから入力途中の文字やスクロール位置が消えません。「関数は毎回呼ばれるが、書き込まれるのは差分だけ」と覚えておいてください。
同じ値を渡すと何も起きない
更新関数に 今と同じ値 を渡した場合、React は変化なしと判断してレンダリングを省くことがあります。
setLikes(likes); // 何も起きない一方、別の state を更新すればコンポーネントは呼び直されるので、そのついでに likes の表示も作り直されます。ただし値は同じなので、見た目は変わりません。
更新はまとめて処理される
1 回のクリックの中で更新関数を何度呼んでも、レンダリングは 1 回にまとめられます。
function handleClick() {
setLikes(likes + 1);
setClicks(clicks + 1);
}途中の中途半端な画面は出ません。2 つの state がそろって新しくなった状態で 1 回だけ描き直されます。これをバッチ処理と呼びます。
つまずきどころ
「関数が毎回呼ばれる」と知ると、重い計算をコンポーネントの中に書いてよいのか不安になります。今の段階では気にしなくて大丈夫です。表示のための軽い計算なら毎回やっても問題になりません。本当に重い処理をどう避けるかは、もっと先の話題です。
もう 1 つ、レンダリングの途中で更新関数を呼んではいけません。return の前に直接 setLikes(...) と書くと、レンダリングがまたレンダリングを呼び、止まらなくなります。更新関数を呼ぶのはイベントの中だけです。
演習
同じ値を渡したときと違う値を渡したときで、画面がどう変わるかを確かめます。