React基礎②のおさらい
// 変数の表示
const name = "Ichigo";
<h1>Hello, {name}!</h1>
// 条件表示
{isLoggedIn ? <Profile /> : <Login />}
{user.isAdmin && <AdminPanel />}
// リスト表示
{todos.map(todo =>
<li key={todo.id}>{todo.text}</li>
)}
{}
でJavaScript、key
は必須
// コンポーネント定義(大文字で開始)
function UserCard({ name, email, children }) {
return (
<div className="card">
<h3>{name}</h3>
<p>{email}</p>
{children}
</div>
);
}
// 使用
<UserCard name="Kurotsuchi Mayuri" email="m.kurotsuchi@seireitei.co.jp">
<button>編集</button>
</UserCard>
props
= 関数の引数、children
= タグの中身
イベントハンドラーはJSXの属性として指定されます
// ボタンクリックの基本
const handleClick = () => {
alert('押したよ!');
};
return <button onClick={handleClick}>押す</button>;
// 複数ボタン + データ渡し
const characters = ['Rena', 'Satoko', 'Rika'];
return (
<div>
{characters.map(name => (
<button key={name} onClick={() => handleSelect(name)}>
{name}
</button>
))}
</div>
);
import { useState } from 'react'; // フックをインポート
// 基本構文
const [likes, setLikes] = useState(0); // 初期値は0
// 数値の更新
setLikes(likes + 1); // 現在の値を使って直接更新
setLikes(prev => prev + 1); // 前の値を使って関数で安全に更新
// 配列の分割代入なし版
const likesState = useState(0);
// likesState[0] = 現在値
// likesState[1] = セッター関数
likesState[1](likesState[0] + 1);
// オブジェクトの更新
const [user, setUser] = useState({ name: 'Ichigo', age: 17 });
setUser({...user, age: 18}); // スプレッド構文で既存保持
// 配列の更新
const [items, setItems] = useState(['bat', 'knife']);
setItems([...items, 'nata']); // 新しい配列を作成
// 3つの要素が必須
const [value, setValue] = useState(''); // 1. 状態
return (
<input
value={value} // 2. input要素
onChange={(e) => setValue(e.target.value)} // 3. ハンドラー関数
/>
);
value
は必ずstate由来onChange
でstateを更新SPAとは、従来のWebアプリとの違い
index.html
から始まる
<!DOCTYPE html>
<html>
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div> <!-- ここにReactがコンテンツを描画 -->
<script src="bundle.js"></script>
</body>
</html>
website/
├── index.html ← ホーム
├── about.html ← 概要ページ
├── contact.html ← お問い合わせ
├── products.html ← 商品一覧
└── ...
各ページ = 別々のHTMLファイル
react-app/
├── index.html ← これ1つだけ!
├── app.jsx ← React アプリケーション
├── styles.css
├── components/
└── ...
全ページ = 1つのHTMLファイル内で切り替え
複数のHTMLファイル
単一のHTMLファイル
ブラウザがHTMLファイルを切り替え
<!-- about.html という別ファイルに移動 -->
<a href="/about.html">概要</a>
<!-- contact.html という別ファイルに移動 -->
<a href="/contact.html">お問い合わせ</a>
JavaScriptでコンテンツを切り替え
// 表示するコンテンツを状態で管理
const [currentPage, setCurrentPage] = useState('home');
const showAbout = () => {
setCurrentPage('about'); // DOMを書き換え
}
// 条件分岐でコンテンツを切り替え
{currentPage === 'home' && <HomeContent />}
{currentPage === 'about' && <AboutContent />}
実際にはルーティングライブラリを使用することが多い
純粋なSPAは多くの問題を抱えており現在は「呪われた」技術とされることも
SPAの問題を解決するレンダリング戦略
コンポーネントの状態管理と描画サイクルを理解する
すべてのコンポーネントは3つの段階を経ます
コンポーネントが作成される
コンポーネントが変更される
コンポーネントが破棄される
Reactの基本原則:同じ入力には同じ出力
props
で常に同じJSXを返すfunction PureComponent({ name }) {
return (
<h1>こんにちは、{name}さん!</h1>
);
}
function ImpureComponent({ name }) {
document.title = `Welcome ${name}`; // 副作用
return <h1>こんにちは、{Math.random()}さん!</h1>; // 非決定的
}
コンポーネントの純粋性を破る操作、でも現実的なアプリには不可欠
Reactの実行フェーズを理解する
JSX → 仮想DOM
コンポーネントが何を表示するかを「計算」する段階
仮想DOM → 実DOM
実際にDOMを「更新」する段階
副作用のタイミングによって使い分ける
イベントハンドラー
レンダリングサイクル外で実行
function Button() {
function handleClick() {
fetch('/api/data'); // 副作用OK
}
return <button onClick={handleClick}>受信する</button>;
}
useEffect
コミットフェーズに実行
function Profile({ userId }) {
useEffect(() => { // userIdが変わったら自動でデータ取得
fetch(`/api/users/${userId}`);
}, [userId]);
return <>...<>;
}
useEffect
の基本形useEffect(setup, dependencies?)
setup
関数dependencies
配列import { useEffect } from 'react';
function MyComponent() {
function setup() {
console.log('副作用を実行');
}
useEffect(setup);
return <div>...</div>;
}
useEffect
の基本構文import { useEffect } from "react";
function Component() {
function cleanup() { // クリーンアップ関数:リソースの解放
console.log("アンマウント時や次のエフェクト実行前");
}
function setup() { // エフェクト関数:副作用を実行
console.log("DOM更新後に実行される");
return cleanup;
}
useEffect(setup, [/* 依存配列 */]);
return <div>...</div>;
}
毎回実行
useEffect(() => {
console.log('毎回のレンダリング後に実行');
}); // 依存配列なし
初回のみ実行
useEffect(() => {
console.log('マウント時のみ実行');
}, []); // 空の依存配列
条件付き実行
useEffect(() => {
console.log('userIdが変わった時のみ実行');
}, [userId]); // userIdを監視して、変わったら実行
useEffect
の実行フローimport { useEffect } from "react";
function Component() {
useEffect(
() => { // エフェクト関数:副作用を実行
console.log("DOM更新後に実行される");
return () => { // クリーンアップ関数:リソースの解放
console.log("アンマウント時や次のエフェクト実行前");
};
},
[/* 依存配列 */]
);
return <div>...</div>;
}
コンポーネントのJSXが仮想DOMに変換され、実DOMが更新される
前回の値と比較し、変更があるかを確認
前回のエフェクトのクリーンアップ関数(ある場合)
セットアップ関数が実行され、副作用処理を行う
useEffect
の動作フローselectedPokemon="jigglypuff"
でPokemonCard
が初回レンダリング
<PokemonCard name="jigglypuff" />
useEffect
が実行されるloading: true
、fetch
でデータ取得開始
useEffect(() => {
setLoading(true);
fetch(`https://pokeapi.co/api/v2/pokemon/jigglypuff`)
...
loading: true → false
、pokemon: null → データ
.then((pokemonData) => {
setPokemon(pokemonData);
setLoading(false);
});
selectedPokemon: "jigglypuff" → "pikachu"
<button onClick={() => setSelectedPokemon("pikachu")}>
name: "jigglypuff" → "pikachu"
<PokemonCard name="pikachu" /> // propsが変更された
[name]
の値が変わったため、useEffectが再度実行される
}, [name]); // "pikachu"に変更されたので再実行
fetch(`https://pokeapi.co/api/v2/pokemon/pikachu`)
useEffect
とライフサイクルuseEffect
実行
依存配列の漏れ
useEffect(() => {
fetchData(userId); // userIdに依存しているが...
}, []); // 依存配列に含まれていない
対策: ESLint plugin-react-hooksを使用
無限ループ
useEffect(() => {
setCount(count + 1); // countに依存
}, [count]); // 無限ループ発生
対策: 関数型更新を使用: setCount(c => c + 1)
クリーンアップ忘れ
useEffect(() => {
const timer = setInterval(updateData, 1000);
// クリーンアップなし → メモリリーク
}, []);
対策: 必ずクリーンアップ関数でリソース解放
ReactのStrict Mode
useLayoutEffect
との違いuseEffect
useLayoutEffect
// DOM要素のサイズを測定する場合のみ
useLayoutEffect(() => {
const rect = ref.current.getBoundingClientRect();
setDimensions({ width: rect.width, height: rect.height });
}, []);
99%の場合はuseEffect
で十分
function ComponentA({ name }) { // A
console.log('Rendering:', name);
return <h1>Hello {name}</h1>;
}
function ComponentB({ name }) { // B
return <h1>Hello {name}</h1>;
}
function ComponentC({ name }) { // C
document.title = name;
return <h1>Hello {name}</h1>;
}
useEffect
はいつ実行される?const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
console.log('Effect runs');
}, [count]);
count
が変更された時のみ
name
が変更された時のみcount
が変更された時のみ実行
function UserStatus({ userId }) {
const [status, setStatus] = useState('offline');
useEffect(() => {
function handleStatusChange(newStatus) {
setStatus(newStatus);
}
StatusAPI.subscribe(userId, handleStatusChange);
}, [userId]);
return <div>Status: {status}</div>;
}
useEffect
の使い方が間違いuserId
が含まれている今日学んだ内容を実際に使ってみよう!
<ZanpakutoLoader>
コンポーネントを作成してください
name
と powerLevel
を表示useState
と useEffect
を組み合わせて実装することfunction ZanpakutoLoader({ name }: { name: string }) {
const [zanpakuto, setZanpakuto] = useState<Zanpakuto | null>(null);
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
// name が変更された時に loadZanpakto を呼び出す処理を書く
}, [name]);
return <div>{/* ローディング、結果、エラーの表示 */}</div>;
}
useEffect
の基本的な使い方React④: カスタムフック、共通化、振り返り