目次
イベント処理と状態管理(useState)
Reactでの「イベント処理」の基本
ブラウザのJSだと:
button.addEventListener('click', () => { ... })みたいに書きますが、Reactではコンポーネントの中でイベントハンドラを渡す形になります。
export default function App() {
function handleClick() {
alert('クリックされました')
}
return (
<button onClick={handleClick}>
クリック
</button>
)
}ポイント:
onClick={handleClick}のように、関数をそのまま渡す(()付けない)- JSXなので
onclickではなく キャメルケースonClick - 他にも
onChange,onSubmit,onKeyDownなど、DOMイベントとほぼ対応
イベントオブジェクト(event)が欲しいときは、普通のJSと同じでOKです。
function handleClick(event) {
console.log(event.target) // クリックされた要素
}Reactの「状態」= useState
Reactで「動くUI」を作るときの基本の型は、「状態(state) + イベント ⇒ 画面を書き換える」です。
useState の基本形:
import { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
// ...
}count:現在の状態setCount:状態を更新するための関数useState(0):初期値 0
イメージ的には:
let count = 0
function setCount(newValue) {
count = newValue
再描画()
}のようなものを、Reactがやってくれている感じです。
シンプルなカウンター
まずは最低限のカウンターから。
import { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1)
}
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={handleClick}>+1</button>
</div>
)
}動き:
- 最初のレンダリング時
useState(0)⇒count = 0- 画面には「現在のカウント: 0」
- ボタンをクリック
handleClickが呼ばれるsetCount(count + 1)⇒setCount(1)- Reactが「状態が変わった」と認識して再レンダリング
- 再レンダリング時
useStateの返す値が1になる- 画面に「現在のカウント: 1」
ポイントは:
- 状態を変えるのは必ず
setCount経由 countを直接書き換えない(count++はNG)
// NG:こうしても画面は変わらない
function handleClick() {
count = count + 1 // ただのローカル変数として扱っているだけ
}Reactは「useState で登録された状態が変わったとき」にだけ再描画する、くらいのイメージを持っておくと整理しやすいです。
状態更新は「非同期的にまとめて」行われる
Reactはパフォーマンスのために、複数の setState をまとめて処理します。
よくある罠:
function App() {
const [count, setCount] = useState(0)
function handleDoubleClick() {
setCount(count + 1)
setCount(count + 1)
}
// ...
}「+1 を2回したから 0 ⇒ 2 だろ」と思いきや、実際は 0 ⇒ 1 にしかなりません。
理由は、2回とも「今の count(=0) を 1 にする」という更新を依頼しているだけだからです。
この場合は、前の状態を元に更新する「関数版」を使うのが正解です。
function handleDoubleClick() {
setCount((prev) => prev + 1)
setCount((prev) => prev + 1)
}ここでは:
- 1回目:
prevは 0 ⇒prev + 1= 1 - 2回目:
prevは 1 ⇒prev + 1= 2
という風に順番に適用されます。
ルール:
「前の値を使って新しい値を計算するときは、必ずsetX((prev) => 〜) の形にする」
これは実務でもかなり頻繁に使います(カウンター・配列操作・トグルなど全部)。
複数の状態を持つ
コンポーネントは useState を何回でも使えます。
import { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
const [message, setMessage] = useState('初期メッセージ')
const [isVisible, setIsVisible] = useState(true)
return (
<div>
<button onClick={() => setCount((prev) => prev + 1)}>
カウント: {count}
</button>
<button onClick={() => setIsVisible((prev) => !prev)}>
メッセージの表示切り替え
</button>
{isVisible && <p>{message}</p>}
</div>
)
}ここでのポイント:
setIsVisible((prev) => !prev)で真偽値トグル{isVisible && <p>...</p>}は「条件付きレンダリング」
オブジェクトや配列を状態にする場合
よくやるのが「1個の state にオブジェクト / 配列を入れる」パターンです。
オブジェクト状態
const [user, setUser] = useState({
name: '山田太郎',
age: 25,
})age だけ変えたいとき:
setUser((prev) => ({
...prev,
age: prev.age + 1,
}))...prevで「前のオブジェクトを展開」- 上書きしたいプロパティだけ後ろに書く
- これを忘れて
{ age: prev.age + 1 }とやるとnameが消えるので注意
配列状態
const [items, setItems] = useState(['apple', 'banana'])要素を追加:
setItems((prev) => [...prev, 'orange'])要素を削除(例えば index 1 だけ除外):
setItems((prev) => prev.filter((_, index) => index !== 1))ここでも共通のルール:
- state は「破壊的変更」しない
push,splice,++ではなく、新しい配列 / オブジェクトを作って渡す。
入力と状態
import { useState } from 'react'
export default function App() {
const [name, setName] = useState('')
function handleChange(e) {
setName(e.target.value)
}
return (
<div>
<input
type="text"
value={name}
onChange={handleChange}
placeholder="名前を入力"
/>
<p>こんにちは、{name || 'ゲスト'} さん</p>
</div>
)
}value={name}⇒ 入力欄の中身を state で管理onChangeでsetName(e.target.value)⇒ 入力されるたびに state 更新- 「入力値 = state」の構造を Controlled Component と呼びます
まとめ
- ReactではイベントハンドラをJSXの属性として渡す(
onClick={handleClick}) useStateで「状態 + セッター関数」を使う- 状態を変えるとReactが再レンダリングしてUIを更新する
- 前の値を参照する更新は必ず
setX(prev => ...)の形にする - オブジェクトや配列のstateは破壊的更新禁止、
...prevやfilter,mapで新しい値を作る
コメント