背景
こんにちは。エンジニアのKennieです。
最近フロントエンドの勉強をし始めてuseStateについて学びました。
公式ドキュメントを読みながら勉強していこうと思い、そこでuseStateについて学んだことを書いてみました!
1. Reactのstateは非同期で更新される
React のstateの更新は非同期で行われます。つまり、stateを更新するために下記のように setAge
を呼び出しても、その時点で即座に変更されるわけではありません。実際の更新は、次のレンダーサイクルまで待たなければなりません。
以下の例では、年齢 (age
) を1ずつ増やすボタンのクリックイベントを扱います。
const [age, setAge] = useState(42);
function handleClick() {
setAge(age + 1);
setAge(age + 1);
setAge(age + 1);
return (
<div>
<p>Current age: {age}</p>
<button onClick={handleClick}>Increase Age</button>
</div>
);
}
↑こちらのコードの動き
https://codesandbox.io/p/sandbox/react-dev-forked-x58vzm?file=%2Fsrc%2FApp.js%3A5%2C1
このコードでは、ボタンを1回押すと、最終的に age
は43になります。なぜなら、すべての setAge(age + 1)
は、最初の age
の値(42)を基に計算されるからです。これは上記に書いたReactの非同期性によるもので、setAge
を呼び出しても、再レンダーされるまで age
の値は更新されないためです。
アップデータ関数を使った更新
function handleClick() {
setAge(preveAge => preveAge + 1); // setAge(42 => 43)
setAge(preveAge => preveAge + 1); // setAge(43 => 44)
setAge(preveAge => preveAge + 1); // setAge(44 => 45)
}
↑こちらのコードの動き
https://codesandbox.io/p/sandbox/react-dev-forked-mg6zxk?file=%2Fsrc%2FApp.js%3A9%2C32
各setAge
の呼び出しが、最新のage
の値を基にして次の値を計算するため、ボタンを1回押すとage
は3回増加し、最終的に+3されます。たとえば、age
が最初に42であれば、1回のクリックで45になります。このように、アップデータ関数を使用すれば前の値を基に新しい値を計算するため、意図通りの結果が得られます。
2. stateの更新におけるオブジェクトと配列
Reactでは、stateにオブジェクトや配列を使用することができますが、既存のオブジェクトを直接変更すべきではないとされています。具体的な例を用いて、その理由を説明します。
例
こちらは既存のオブジェクトを直接変更している例
const [userProfile, setUserProfile] = useState({ username: '', email: '' });
userProfile.username = 'Kennie';
こちらは置き換えをしている例
setUserProfile({
...userProfile,
username: 'Kennie'
});
解説
- 既存のオブジェクトを直接変更している場合: Reactではstateにある既存のオブジェクトを直接変更するとReactはその変更を検知できません。結果として、Reactが再レンダリングされたとき、username の値は 'Kennie' ではなく、依然として空の文字列 '' のままです。
-
新しいオブジェクトの作成: スプレッド構文(
...userProfile
)を使用することで、元のuserProfile
オブジェクトのプロパティをコピーし、新しいオブジェクトを作成します。この新しいオブジェクトは、元のものとは異なるメモリ領域に保存されるため、参照が変わり、Reactはstateの変更を正しく検知できるようになります。
参考リンク
あとがき
公式ドキュメントにはuseStateの様々な使い方が書かれていて、将来的にここで得た情報は開発をしている際、もしくはバグに出くわした際、役立ってくると感じました。