リアクティブデータ — useState
useState は React で最も重要な Hook です。この章では React の状態管理の仕組みを理解し、カテゴリ絞り込み機能を実装します。
Reactの「リアクティブ」とVue3の違いは?
Vue3 では ref/reactive を使ってリアクティブデータを作成し、データが変わるとビューが自動的に更新されます。
React の発想は異なります:state を変更すると、React は新しい state でコンポーネント関数を再実行し、新しいビューをレンダリングします。。
これが React という名前の由来です——コンポーネントが state の変化に「反応」します。
useState 基本の使い方
useStateこれは React で最も基本的な Hook で、関数コンポーネントに状態を追加するために使います。
実例
import { useState } from 'react'
function Counter() {
// useState(初期値) は配列を返します: [現在の値, 更新関数]
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1) // 状態を更新すると、React が自動的にコンポーネントを再レンダリングします
}
return (
<div>
<p>クリック回数:{count}</p>
<button onClick={handleClick}>+1</button>
</div>
)
}
ボタンをクリックするたびに、setCountcount の値を更新すると、React は新しい値で Counter 関数を再実行し、新しい JSX を返すため、ページが自動的に更新されます。
React の規約:状態名には
[something, setSomething]分割代入を使います。set で始まる関数は慣習的な命名規則であり、必須ではありませんが、コミュニティ全体がこのように書いています。
なぜstateを直接変更できないのか?
React では、state は読み取り専用です。setter 関数を通して更新する必要があり、直接変更することはできません。
実例
function ArticleManager() {
const [title, setTitle] = useState('默认标题')
// 間違った書き方:直接変更
// title = '新标题' // React は変更に気づかないため、再レンダリングされません
// 正しい書き方:setter で更新
function updateTitle() {
setTitle('新标题') // React は状態が変わったことを認識し、再レンダリングします
}
return (
<div>
<p>{title}</p>
<button onClick={updateTitle}>タイトルを変更</button>
</div>
)
}
この背景には React の不変(Immutable)原則があります:古いデータを変更するのではなく、新しいデータで置き換えます。
配列とオブジェクトを更新する正しい方法
配列とオブジェクトについては、直接 push、splice、またはプロパティの変更はできません。新しいコピーを作成する必要があります。
実例
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习 React', done: false },
{ id: 2, text: '写博客', done: true }
])
// 項目を追加:古い配列を展開 + 新しい要素
function addTodo(text) {
const newTodo = { id: Date.now(), text, done: false }
setTodos([...todos, newTodo]) // スプレッド演算子で新しい配列を作成
}
// 項目を修正:map で走査し、変更対象を見つけて新しいオブジェクトを返す
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
))
}
// 項目を削除:filter で不要なものを取り除く
function removeTodo(id) {
setTodos(todos.filter(todo => todo.id !== id))
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)}>
{todo.done ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
)
}
| 操作 | 間違った書き方 | 正しい書き方 |
|---|---|---|
| 追加 | arr.push(item) | setArr([...arr, item]) |
| 修正 | arr[0] = newVal | setArr(arr.map(el => el.id === id ? ... : el)) |
| 削除 | arr.splice(i, 1) | setArr(arr.filter(el => el.id !== id)) |
| オブジェクトのプロパティ変更 | obj.name = 'xxx' | setObj({ ...obj, name: 'xxx' }) |
核心原則:常に新しい配列/新しいオブジェクトで古いものを置き換える。スプレッド演算子
...と学んだ配列メソッド(map、filter)は、不変性更新の最良のパートナーです。
useMemo — 派生計算値
useMemo計算結果をキャッシュするために使います。依存関係が変わらないときは再計算をスキップし、不要なパフォーマンスコストを回避します。
実例
function ArticleFilter() {
const [articles] = useState([
{ id: 1, title: 'React 入门', category: 'React' },
{ id: 2, title: 'Promise 详解', category: 'JavaScript' },
{ id: 3, title: 'Grid 布局', category: 'CSS' },
{ id: 4, title: 'React Hooks', category: 'React' }
])
const [activeCategory, setActiveCategory] = useState('全部')
// useMemo がフィルタ結果をキャッシュし、依存関係が変わったときだけ再計算
const filteredArticles = useMemo(() => {
console.log('重新计算过滤结果') // キャッシュ効果を検証
if (activeCategory === '全部') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory]) // 依存配列
// useMemo で統計データを計算
const totalCount = useMemo(() => articles.length, [articles])
const filteredCount = useMemo(() => filteredArticles.length, [filteredArticles])
return (
<div>
<p>共 {totalCount}件、現在{filteredCount} 篇</p>
<button onClick={() => setActiveCategory('全部')}>すべて</button>
<button onClick={() => setActiveCategory('React')}>React</button>
<button onClick={() => setActiveCategory('JavaScript')}>JavaScript</button>
<button onClick={() => setActiveCategory('CSS')}>CSS</button>
{filteredArticles.map(a => (
<div key={a.id}><h3>{a.title}</h3><span>{a.category}</span></div>
))}
</div>
)
}
useMemo(fn, deps) の依存配列は完全に記入する必要があります:関数内で使用するすべてのコンポーネント内部変数を配列に宣言する必要があります。React は浅い比較によって再計算するかどうかを決定します。
実践:ブログにカテゴリ絞り込みを追加する
実例
import { useState, useMemo } from 'react'
import './App.css'
function App() {
const [articles] = useState([
{ id: 1, title: 'React 入门完全指南', summary: 'ゼロから React を学ぶ', category: 'React', date: '2024-05-10' },
{ id: 2, title: 'JS 异步编程详解', summary: 'Promise と async/await を理解する', category: 'JavaScript', date: '2024-05-08' },
{ id: 3, title: 'CSS Grid 布局实战', summary: '用 Grid 实现响应式布局', category: 'CSS', date: '2024-05-05' },
{ id: 4, title: 'React Hooks 深入', summary: 'useState と useEffect を深く理解する', category: 'React', date: '2024-05-03' },
{ id: 5, title: 'Flexbox 完全指南', summary: '一文学会弹性布局', category: 'CSS', date: '2024-05-01' },
])
const [activeCategory, setActiveCategory] = useState('全部')
// すべてのカテゴリを抽出(重複除去)
const categories = useMemo(() => {
const cats = articles.map(a => a.category)
return ['全部', ...new Set(cats)]
}, [articles])
// カテゴリに基づいて記事をフィルタリング
const filteredArticles = useMemo(() => {
if (activeCategory === '全部') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])
return (
<div className="app">
<header className="navbar">
<h1 className="logo">サンプル Blog</h1>
<nav>
<a href="/">ホーム</a>
<a href="#">概要</a>
</nav>
</header>
<main className="container">
<h2 className="section-title">最新記事</h2>
{/* カテゴリ絞り込みボタングループ */}
<div className="category-bar">
{categories.map(cat => (
<button
key={cat}
className={activeCategory === cat ? 'active' : ''}
onClick={() => setActiveCategory(cat)}
>
{cat}
</button>
))}
</div>
<p className="result-info">共 {filteredArticles.length} 篇</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">このカテゴリにはまだ記事がありません</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<div key={article.id} className="article-card">
<div className="card-content">
<span className="card-category">{article.category}</span>
<h3>{article.title}</h3>
<p>{article.summary}</p>
<span className="card-date">{article.date}</span>
</div>
</div>
))}
</div>
)}
</main>
<footer className="footer">
<p>© 2024 サンプル Blog. Powered by React.</p>
</footer>
</div>
)
}
export default App
実例
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar button {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
background: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.category-bar button.active {
background: #1976d2;
color: #fff;
border-color: #1976d2;
}
.category-bar button:hover {
border-color: #1976d2;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
本章のまとめ
この章では、React の状態管理の3つの核心概念を習得しました:useState による状態の作成と更新、不変更新の原則(スプレッド演算子 + map/filter)、useMemo による派生計算値のキャッシュです。
カテゴリ絞り込み機能を通じて、React の「setState → 再レンダリング → 新しいビュー」というデータ駆動パターンを体験しました。
その他の拡張