Reactとは?2026年も最前線で使われる理由
React(リアクト)はMeta(旧Facebook)が開発したJavaScriptライブラリで、2026年現在もフロントエンド開発の主流として広く使われています。コンポーネントベースのアーキテクチャと仮想DOMによるパフォーマンスの高さが特徴です。
React環境構築
Viteを使った高速セットアップ
# Viteを使ってReactプロジェクトを作成(2026年推奨)
npm create vite@latest my-react-app -- --template react-ts
cd my-react-app
npm install
npm run dev
React Hooksの完全マスター
useState – 状態管理の基本
import React, { useState } from 'react';
interface Task {
id: number;
title: string;
completed: boolean;
}
const TodoApp: React.FC = () => {
const [tasks, setTasks] = useState([]);
const [inputValue, setInputValue] = useState('');
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');
const addTask = () => {
if (!inputValue.trim()) return;
const newTask: Task = {
id: Date.now(),
title: inputValue,
completed: false,
};
setTasks(prev => [...prev, newTask]);
setInputValue('');
};
const toggleTask = (id: number) => {
setTasks(prev =>
prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
)
);
};
const filteredTasks = tasks.filter(task => {
if (filter === 'active') return !task.completed;
if (filter === 'completed') return task.completed;
return true;
});
return (
<div className="todo-app">
<h1>タスク管理アプリ</h1>
<div className="input-area">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTask()}
placeholder="タスクを入力..."
/>
<button onClick={addTask}>追加</button>
</div>
<div className="filters">
{(['all', 'active', 'completed'] as const).map(f => (
<button
key={f}
className={filter === f ? 'active' : ''}
onClick={() => setFilter(f)}
>
{f === 'all' ? '全て' : f === 'active' ? '未完了' : '完了'}
</button>
))}
</div>
<ul className="task-list">
{filteredTasks.map(task => (
<li
key={task.id}
className={task.completed ? 'completed' : ''}
onClick={() => toggleTask(task.id)}
>
{task.title}
</li>
))}
</ul>
<p>残り: {tasks.filter(t => !t.completed).length} タスク</p>
</div>
);
};
useEffect – 副作用の管理
import React, { useState, useEffect, useRef } from 'react';
interface Post {
id: number;
title: string;
body: string;
}
const DataFetcher: React.FC = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const abortControllerRef = useRef(null);
useEffect(() => {
// クリーンアップのためAbortControllerを使用
abortControllerRef.current = new AbortController();
const fetchPosts = async () => {
try {
setLoading(true);
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts?_limit=10',
{ signal: abortControllerRef.current!.signal }
);
if (!response.ok) throw new Error('データの取得に失敗しました');
const data = await response.json();
setPosts(data);
} catch (err) {
if (err instanceof Error && err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
};
fetchPosts();
// クリーンアップ関数
return () => {
abortControllerRef.current?.abort();
};
}, []); // 空配列 = マウント時のみ実行
if (loading) return <div>読み込み中...</div>;
if (error) return <div>エラー: {error}</div>;
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</li>
))}
</ul>
);
};
useContext – グローバル状態管理
import React, { createContext, useContext, useState, ReactNode } from 'react';
// テーマのコンテキスト定義
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext(undefined);
// カスタムフック
const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
// プロバイダーコンポーネント
export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className={theme}>{children}</div>
</ThemeContext.Provider>
);
};
// 使用例
const Header: React.FC = () => {
const { theme, toggleTheme } = useTheme();
return (
<header>
<h1>Tech Athletes</h1>
<button onClick={toggleTheme}>
{theme === 'light' ? '🌙 ダークモード' : '☀️ ライトモード'}
</button>
</header>
);
};
カスタムHooksで再利用可能なロジックを作る
import { useState, useCallback, useEffect } from 'react';
// ローカルストレージとの同期
function useLocalStorage(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = useCallback((value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
}, [key, storedValue]);
return [storedValue, setValue] as const;
}
// デバウンス
function useDebounce(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// 使用例
const SearchComponent: React.FC = () => {
const [searchTerm, setSearchTerm] = useLocalStorage('lastSearch', '');
const debouncedSearch = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearch) {
console.log('検索実行:', debouncedSearch);
}
}, [debouncedSearch]);
return (
<input
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="検索(500ms後に実行)"
/>
);
};
Reactパフォーマンス最適化
React.memo・useMemo・useCallbackの使い方
import React, { useState, useMemo, useCallback, memo } from 'react';
interface Product {
id: number;
name: string;
price: number;
category: string;
}
// React.memoで不要な再レンダリングを防ぐ
const ProductCard = memo<{ product: Product; onAddToCart: (id: number) => void }>(
({ product, onAddToCart }) => {
console.log('ProductCard rendered:', product.id);
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>¥{product.price.toLocaleString()}</p>
<button onClick={() => onAddToCart(product.id)}>カートに追加</button>
</div>
);
}
);
const ProductList: React.FC = () => {
const [products] = useState([
{ id: 1, name: 'MacBook Pro', price: 248800, category: 'PC' },
{ id: 2, name: 'iPhone 17', price: 124800, category: 'スマホ' },
{ id: 3, name: 'AirPods Pro', price: 39800, category: 'イヤホン' },
]);
const [cart, setCart] = useState([]);
const [filterCategory, setFilterCategory] = useState('');
// useMemo: 重い計算のメモ化
const filteredProducts = useMemo(() => {
return filterCategory
? products.filter(p => p.category === filterCategory)
: products;
}, [products, filterCategory]);
const totalPrice = useMemo(() => {
return cart.reduce((sum, id) => {
const product = products.find(p => p.id === id);
return sum + (product?.price || 0);
}, 0);
}, [cart, products]);
// useCallback: 関数のメモ化
const handleAddToCart = useCallback((id: number) => {
setCart(prev => [...prev, id]);
}, []);
return (
<div>
<p>カート合計: ¥{totalPrice.toLocaleString()}</p>
{filteredProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</div>
);
};
Reactのテスト(Vitest + Testing Library)
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { TodoApp } from './TodoApp';
describe('TodoApp', () => {
it('初期状態でタスクリストが空であること', () => {
render(<TodoApp />);
expect(screen.getByText('残り: 0 タスク')).toBeInTheDocument();
});
it('新しいタスクを追加できること', async () => {
const user = userEvent.setup();
render(<TodoApp />);
const input = screen.getByPlaceholderText('タスクを入力...');
await user.type(input, '新しいタスク');
await user.click(screen.getByText('追加'));
expect(screen.getByText('新しいタスク')).toBeInTheDocument();
expect(screen.getByText('残り: 1 タスク')).toBeInTheDocument();
});
it('タスクをクリックで完了状態にできること', async () => {
const user = userEvent.setup();
render(<TodoApp />);
// タスクを追加
const input = screen.getByPlaceholderText('タスクを入力...');
await user.type(input, 'テストタスク');
await user.click(screen.getByText('追加'));
// タスクをクリックして完了にする
await user.click(screen.getByText('テストタスク'));
expect(screen.getByText('残り: 0 タスク')).toBeInTheDocument();
});
});
まとめ:React習得のロードマップ
| ステップ | 学習内容 | 期間の目安 |
|---|---|---|
| Step 1 | JavaScript・TypeScript基礎 | 1〜2ヶ月 |
| Step 2 | React基礎(JSX、Props、State) | 1〜2ヶ月 |
| Step 3 | Hooks完全習得 | 2〜3ヶ月 |
| Step 4 | 状態管理(Zustand/Jotai) | 1〜2ヶ月 |
| Step 5 | Next.js(SSR/SSG) | 2〜3ヶ月 |
| Step 6 | テスト・パフォーマンス最適化 | 2〜3ヶ月 |
Reactは2026年も最前線で使われているフロントエンドライブラリです。本記事で紹介した知識を身につけ、実際にプロジェクトを作りながら習得していきましょう。