はじめに
Reactを学習し始めたころ、useState
のset関数には更新用関数を設定することができると学びました。
しかし、今のところ業務でそれらしきコードを見たことがありません。
また、公式サイトで示されている例を見ても、あまりその実用性がピンときていません。
そこで、どんな場面で使えるのかを考えてみます。
更新用関数とは
コードは以下の通りです。
import { useState } from "react";
const App = () => {
const [state, setState] = useState(0);
const handleClick = () => {
setState(state + 1);
setState(state + 1);
setState(state + 1);
};
return (
<>
<div>{state}</div>
<button onClick={handleClick}>count up</button>
</>
);
};
export default App;
setState
を3回呼び出して、state
を3回更新しています。
初期値が0なので、1回のボタンクリックで3ずつ増える想定です。
しかしボタンを押すとわかりますが、実際には1しか増えません。
これは、setState
を実行した時点ではまだstate
は更新されていないためです。
更新前のstate
を参照しているので、何度setState
を呼び出しても初期値の0に対して1を追加する、という処理を繰り返しているだけです。
このような挙動を回避するため、setState
にわたすのが更新用関数です。
以下のようにsetState
に関数を渡すことで、参照するstate
が更新後のstate
であることを保証してくれます。
const handleClick = () => {
setState((prevState) => prevState + 1);
setState((prevState) => prevState + 1);
setState((prevState) => prevState + 1);
};
更新用関数は引数に直前のstate
を受け取ります。
変数名は自由ですが、一般的にはstateの変数の1文字目、または頭にprev
とつけるそうです。
というのが更新用関数の説明です。
更新用関数、使うか?
さて、更新用関数が何であるかを改めて確認しましたが、これって使うんでしょうか。
上記で紹介したようなコードは実際のお仕事ではまず見かけません。
基本的には更新したい値をそのままset関数に指定すれば、それでオッケーな気がします。
よって、あまり使い道はないように思っています。
実際、公式サイトでも次のように言っています。
ほとんどのケースでは、どちらのアプローチでも違いはありません。
ということなので、基本的にはあまり意識しなくてもよさそうです。
使う場面としては以下のような場面とのこと。
一方で、同じイベント内で複数回の更新を行う場合、更新用関数が役に立ちます。また、state 変数自身を参照することが難しいケースにも有用です
ただ、これってどういう場面なんでしょう
ChatGPTの力も借りながら、ちょっと考えてみます。
同じイベント内で複数回の更新を行う
考えられるのは、「複数のチェックボックスをまとめてチェックする」でしょうか。
以下はコード例です。
※TypeScriptで書いています。
import React, { useState } from "react";
interface ListItem {
id: number;
text: string;
checked: boolean;
}
const App: React.FC = () => {
const [items, setItems] = useState<ListItem[]>([
{ id: 1, text: "アイテム 1", checked: false },
{ id: 2, text: "アイテム 2", checked: false },
{ id: 3, text: "アイテム 3", checked: false },
]);
const checkAllItems = () => {
setItems((prevItems) =>
prevItems.map((item) => ({ ...item, checked: true }))
);
};
const handleChange = (id: number) => {
setItems((prevItems) =>
prevItems.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
)
);
};
return (
<div>
<button onClick={checkAllItems}>まとめてチェック</button>
<ul>
{items.map((item) => (
<li key={item.id}>
<label>
<input
type="checkbox"
checked={item.checked}
onChange={() => handleChange(item.id)}
/>
{item.text}
</label>
</li>
))}
</ul>
</div>
);
};
export default App;
と、書いていて気づきましたが、これ実際は複数回の更新ではなさそうですね。
set関数を呼んでいるのは一度だけなので、ちょっと違いそうです。
ただ、ポイントとしてはcheckAllItems
とhandleChange
ともに更新用関数を使用して値を更新しているという点です。
これにより、直前がどのような状態であっても、更新後のstate
を使って確実に処理をしてくれるので、とくにまとめてチェックの場合にはおかしな結果とならずに済みそうです。
state変数自身を参照することが難しい場合
これはちょっと私の文章読解能力では理解しきれなかったのですが、自分自身に対して繰り返し処理をするものだと理解すると、例えば経過時間を表示するタイマーとかが考えられるかもしれません(あまり実用的ではないですが)
以下は間違ったコード例です
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div>{count}秒経過</div>;
}
export default App;
これを画面に表示させるとわかりますが、ずっと1秒のままです。
これを、更新用関数を設定すると以下のようになります。
const timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
更新用関数によって、更新後の値であることが保証されるので以下のように秒数が増えていきます。
まとめ
なんとか更新用関数の使い道を探ろう、と意気込んでChatGPTやClaudeAIにも聞いて色々考えてみましたが、正直な話絶対に更新用関数を使わなければならない場面というのはそうそうないように思いました。
最後のタイマーは確かに更新用関数がないと難しいかもしれませんが、これが現実的に生きてくる場面はあまりない気がしました。
なんとなく、徒労に終わった感じも否めないですが、今回は以上です。