
React入門ガイド始めるよ!
現代のWeb開発において、React(リアクト)は最も人気の高いJavaScriptライブラリの一つです。Facebook(現Meta)が開発したReactは、ユーザーインターフェース(UI)の構築を効率的に行うための強力なツールです。この記事では、React初心者の方でも理解しやすいように、基本概念から実践的な使い方まで、段階的に解説していきます。
Reactを学ぶことで、モダンなWebアプリケーションを構築する能力が身につき、フロントエンド開発者としてのスキルを大幅に向上させることができます。本記事では、実際のコードサンプルを豊富に用いながら、Reactの核となる概念を一つずつ丁寧に説明していきます。
Reactとは
Reactは、Facebookが開発したJavaScriptライブラリで、ユーザーインターフェースの構築に特化しています。従来のWebページ開発とは異なり、Reactでは「コンポーネント」という概念を使って、再利用可能なUI部品を作成します。
Reactの主な特徴
- コンポーネントベース:UIを小さな部品に分割して管理
- 仮想DOM:効率的な画面更新を実現
- 宣言的:「どのような見た目にするか」を記述
- 再利用性:一度作ったコンポーネントを何度でも使用可能
コンポーネントの作成とネスト
コンポーネントはReactアプリの“部品”です。ボタンやヘッダー、リストなど、あらゆるUIの要素を小さな単位(コンポーネント)として作り、それらを組み合わせてアプリ全体を組み立てます。
基本的なコンポーネントの作成
function WelcomeMessage() {
return (
<div>
<h2>こんにちは、React の世界へようこそ!</h2>
<p>これは私の最初のReactコンポーネントです。</p>
</div>
);
}
- 関数コンポーネントの定義
function WelcomeMessage() { ... }
の形式で定義されています。これは、React 16.8以降で推奨されている関数コンポーネントの形式です。- コンポーネント名は慣習的にパスカルケース(例:
WelcomeMessage
)で始まります。
- JSX (JavaScript XML)
return (...)
の中に書かれている<div>
,<h2>
,<p>
といったHTMLのような記述は、ReactのJSX (JavaScript XML) と呼ばれる構文です。JavaScriptのコード内でHTMLのような要素を記述することができ、これによりUIの構造を直感的に表現できます。- JSXはBabelのようなツールによって最終的に通常のJavaScript(
React.createElement()
の呼び出し)に変換されます。
- 単一のルート要素
- Reactコンポーネントは、
return
文で必ず1つのルート要素を返す必要があります。この例では、<div>
がそのルート要素となっています。 - 複数の要素を返したい場合は、
<></>
(Fragmentと呼ばれる短縮構文)を使用することもできますが、このコンポーネントでは単一の<div>
で囲んでいます。
- Reactコンポーネントは、
- 静的コンテンツの表示:
- このコンポーネントは、外部からのデータを受け取らず、
<h2>
と<p>
タグ内に固定された文字列を表示しています。
- このコンポーネントは、外部からのデータを受け取らず、
コンポーネントのネスト
コンポーネントは他のコンポーネントの中で使用できます。これをネストと呼びます。
このコード例は、Reactにおけるコンポーネントのネスト(入れ子構造) の概念を示しています。複数の小さなコンポーネントを組み合わせて、より大きなUI(ユーザーインターフェース)を構築する方法が分かります。
function Header() {
return (
<header>
<h1>私のウェブサイト</h1>
<nav>
<ul>
<li>ホーム</li>
<li>サービス</li>
<li>お問い合わせ</li>
</ul>
</nav>
</header>
);
}
function MainContent() {
return (
<main>
<WelcomeMessage /> {/* WelcomeMessage コンポーネントがある前提です */}
<p>ここにメインコンテンツが表示されます。</p>
</main>
);
}
function App() {
return (
<div>
<Header />
<MainContent />
</div>
);
}
App
コンポーネントは、Reactアプリケーションの最上位(ルート)コンポーネントとして機能することが多いです。- このコンポーネントは、
<Header />
と<MainContent />
という2つのコンポーネントを<div>
要素の中にネストして呼び出しています。 - これにより、
Header
コンポーネントがレンダリングする内容(ヘッダー)と、MainContent
コンポーネントがレンダリングする内容(メインコンテンツと、その中にネストされたWelcomeMessage
)が、縦に並んで表示されるウェブページのような構造が構築されます。
JSXでマークアップを書く
JSXは、JavaScriptの中でHTMLライクな記法を使える構文拡張です。Reactでは、JSXを使ってUIの構造を記述します。
JSXの基本ルール
function ProductCard() {
const productName = "MacBook Pro";
const price = 248000;
const isAvailable = true;
return (
<>
<div className="product-card">
<h3>{productName}</h3>
<p>価格: ¥{price.toLocaleString()}</p>
<p>在庫: {isAvailable ? "あり" : "なし"}</p>
<img src="/macbook.jpg" alt={productName} />
</div>
</>
);
}
- すべてのタグは閉じる必要がある
- HTMLでは
<img>
や<input>
のように閉じタグが不要な要素がありますが、JSXではすべての要素に閉じタグが必要です。 - 閉じタグがない単一の要素(例:
<img>
)は、<img ... />
のように自己終了タグとして記述します。 - このコードでは、
div
,h3
,p
は通常の閉じタグを持ち、img
は自己終了タグとして記述されています。
- HTMLでは
- 複数の要素を返すには、親要素で囲む
- Reactコンポーネントの
return
文は、必ず1つの親要素を返さなければなりません。 - この
ProductCard
コンポーネントでは、<div className="product-card">
という単一のdiv
要素が返されています。 - もし、
div
要素の他に、例えば別の<span>
要素を並列で返したい場合は、それらをさらに別の<div>
で囲むか、またはFragmentと呼ばれる短縮構文<></>
で囲む必要があります。このコードでは<>...</>
が使われていますが、これはdiv
要素が一つしかないため、厳密には不要ですが、慣習的に使われることもあります。FragmentはDOMに余計なノードを追加せずに複数の要素をグループ化する際に非常に便利です。
- Reactコンポーネントの
- JavaScriptの式は波括弧
{}
で囲む- JSX内でJavaScriptの変数、関数呼び出し、式などを使いたい場合は、それらを波括弧
{}
で囲む必要があります。 <h3>{productName}</h3>
:productName
というJavaScript変数の値が表示されます。<p>価格: ¥{price.toLocaleString()}</p>
:price
変数の値をtoLocaleString()
メソッドでカンマ区切りに整形した結果が表示されます。
- JSX内でJavaScriptの変数、関数呼び出し、式などを使いたい場合は、それらを波括弧
JSXでの条件分岐
function UserGreeting({ userName, isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h2>おかえりなさい、{userName}さん!</h2>
) : (
<h2>ゲストユーザーでログインしています</h2>
)}
</div>
);
}
- JSX内で
{...}
の中に三項演算子(? :
)を使い、状況によって表示する内容を切り替えています。 - 三項演算子は「
条件 ? 真のとき : 偽のとき
」という形で使います。
スタイルの追加方法
Reactでは、様々な方法でスタイルを適用できます。CSS、インラインスタイル、CSS-in-JSなど、プロジェクトの要件に応じて選択できます。
CSSファイルを使用する方法
// styles.css
.card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 20px;
margin: 10px;
}
.card-title {
color: #333;
font-size: 1.5em;
margin-bottom: 10px;
}
これは一般的なCSSの記述方法です。.card
と.card-title
という2つのCSSクラスが定義されています。
それぞれのクラスは、背景色、角の丸み、影、余白、文字の色やサイズなど、UI要素の見た目を決定するプロパティを持っています。
これらのスタイルは、HTML要素にこれらのクラス名が付与された際に適用されます。
// React コンポーネント
import './styles.css';
function NewsCard({ title, content, date }) {
return (
<div className="card">
<h3 className="card-title">{title}</h3>
<p>{content}</p>
<small>投稿日: {date}</small>
</div>
);
}
- CSSファイルのインポート
import './styles.css';
の行がこのコードの肝です。これにより、定義されたstyles.css
ファイルがこのReactコンポーネント(またはアプリケーション全体)に読み込まれます。- ビルドツール(WebpackやViteなど)がこの
import
文を処理し、CSSルールが最終的なウェブページに適用されるようにします。
- CSSクラスの適用
div
要素にはclassName="card"
が、h3
要素にはclassName="card-title"
がそれぞれ適用されています。- HTMLでは
class
属性を使いますが、JSXではJavaScriptの予約語との衝突を避けるため、代わりにclassName
を使用します。 - この
className
にstyles.css
で定義したクラス名を指定することで、それぞれのHTML要素にCSSのスタイルが適用されます。
インラインスタイルを使用する方法
function StatusBadge({ status }) {
const badgeStyle = {
padding: '5px 10px',
borderRadius: '4px',
fontSize: '0.8em',
fontWeight: 'bold',
color: 'white',
backgroundColor: status === 'active' ? '#28a745' : '#dc3545',
};
return (
<span style={badgeStyle}>
{status === 'active' ? '有効' : '無効'}
</span>
);
}
- スタイルを定義するJavaScriptオブジェクト
const badgeStyle = { ... };
のように、JavaScriptのオブジェクトとしてスタイルを定義しています。- CSSのプロパティ名(例:
background-color
)は、JavaScriptではキャメルケース(例:backgroundColor
)で記述します。値は文字列で指定します。
- 条件に応じて動的にスタイルを変更
backgroundColor: status === 'active' ? '#28a745' : '#dc3545',
の部分が重要です。- ここでは三項演算子を使用し、
status
が'active'
であれば背景色を緑(#28a745
)に、そうでなければ赤(#dc3545
)に設定しています。これにより、バッジの視覚的なフィードバックが動的に変わります。
style
属性でJavaScriptオブジェクトを渡す<span style={badgeStyle}>
のように、HTML要素のstyle
属性に、先ほど定義したbadgeStyle
というJavaScriptオブジェクトを波括弧{}
で囲んで渡しています。- この記法により、JavaScriptで定義したスタイルが直接その要素に適用されます。
データの表示
Reactでは、JavaScriptの変数やオブジェクトのデータを簡単にUIに表示できます。波括弧 {} を使って、動的なデータを埋め込むことができます。
基本的なデータの表示
function UserProfile() {
const user = {
name: "田中太郎",
age: 28,
email: "tanaka@example.com",
avatar: "/avatar.jpg",
skills: ["JavaScript", "React", "Node.js"],
};
return (
<div className="user-profile">
<img src={user.avatar} alt={`${user.name}のアバター`} />
<h2>{user.name}</h2>
<p>年齢: {user.age}歳</p>
<p>メール: {user.email}</p>
<p>スキル: {user.skills.join(', ')}</p>
</div>
);
}
- JSX内でのオブジェクトプロパティへのアクセス
<img src={user.avatar} ... />
や<h2>{user.name}</h2>
のように、JSX内で波括弧{}
を使用して、定義したuser
オブジェクトの各プロパティ(例:user.avatar
,user.name
)にアクセスし、その値をHTML要素内に表示しています。alt
属性のように文字列リテラルとJavaScriptの式を組み合わせる場合は、テンプレートリテラル (`${user.name}のアバター`
) を使用すると便利です。
- 配列データの整形と表示
skills
プロパティは["JavaScript", "React", "Node.js"]
という配列です。<p>スキル: {user.skills.join(', ')}</p>
のように、配列のjoin(', ')
メソッドを使って、配列の要素をカンマとスペースで区切った一つの文字列に変換して表示しています。これにより、複数のスキルがきれいに一行で表示されます。
計算結果の表示
function PriceCalculator() {
const price = 1200;
const tax = 0.1;
const quantity = 3;
return (
<div>
<h3>料金計算</h3>
<p>単価: ¥{price.toLocaleString()}</p>
<p>数量: {quantity}個</p>
<p>小計: ¥{(price * quantity).toLocaleString()}</p>
<p>税込価格: ¥{Math.floor(price * quantity * (1 + tax)).toLocaleString()}</p>
</div>
);
}
- JavaScriptの計算とJSXへの埋め込み
- JSX内では波括弧
{}
を使うことで、JavaScriptの式を埋め込むことができます。 <p>小計: ¥{(price * quantity).toLocaleString()}</p>
のように、price * quantity
という計算式の結果が直接表示されます。
- JSX内では波括弧
- 数値のフォーマット (
toLocaleString()
)price.toLocaleString()
や(price * quantity).toLocaleString()
のように、toLocaleString()
メソッドを使用することで、数値が適切な地域の通貨形式(例: 日本では3桁ごとのカンマ区切り) に変換されて表示されます。これにより、大きな数値でも非常に読みやすくなります。
- 小数点以下の処理 (
Math.floor()
)税込価格
の計算では、Math.floor(price * quantity * (1 + tax))
を使用しています。(1 + tax)
は税率を含んだ乗数(例: 1.1)を表します。Math.floor()
は、計算結果の小数部分を切り捨てて整数に変換します。これにより、価格が円単位で正確に表示されます
条件付きレンダリング
条件付きレンダリングは、特定の条件に基づいて異なるUIを表示する機能です。Reactでは、JavaScriptの条件文をそのまま使用できます。
三項演算子を使用した条件分岐
function WeatherDisplay({ temperature, isRaining }) {
return (
<div className="weather-widget">
<h3>今日の天気</h3>
<p>気温: {temperature}°C</p>
<p>
{isRaining ? (
<span style={{ color: 'blue' }}>☔ 雨が降っています</span>
) : (
<span style={{ color: 'orange' }}>☀️ 晴れています</span>
)}
</p>
<p>
服装:{" "}
{temperature < 15
? "厚手のコート"
: temperature < 25
? "軽いジャケット"
: "半袖でOK"}
</p>
</div>
);
}
このコードは、Reactで三項演算子(Ternary Operator) を使って、特定の条件に基づいて異なるUI要素やテキストをレンダリングする方法を示しています。これは、if/else
文をJSX内で簡潔に表現する非常に一般的なパターンです。
isRaining ? ... : ...
: これが三項演算子の基本形です。
isRaining
がtrue
の場合、?
の直後の部分(青い文字で「☔ 雨が降っています」と表示される<span>
)がレンダリングされます。isRaining
がfalse
の場合、:
の直後の部分(オレンジ色の文字で「☀️ 晴れています」と表示される<span>
)がレンダリングされます。
temperature
の部分では、(気温)に基づいて推奨される服装を決定しています。複数の条件をチェックするために、三項演算子がネスト(入れ子) になっています。
論理演算子を使用した条件表示
function NotificationCenter({ notifications, hasNewMessage }) {
return (
<div>
<h3>通知センター</h3>
{hasNewMessage && (
<div className="new-message-alert">
🔔 新しいメッセージがあります
</div>
)}
{notifications.length > 0 ? (
<ul>
{notifications.map(notification => (
<li key={notification.id}>{notification.message}</li>
))}
</ul>
) : (
<p>通知はありません</p>
)}
</div>
);
}
このNotificationCenter
コンポーネントは、Reactで論理AND演算子 (&&
) と三項演算子を組み合わせて、特定の条件が満たされた場合にのみUI要素を表示する、条件付きレンダリングのパターンを示しています。
- 論理AND演算子 (
&&
) を使用した条件表示{hasNewMessage && (...) }
の部分がこのパターンです。- JavaScriptのルールでは、
true && 任意の式
の場合、任意の式
が評価され、その結果が返されます。 false && 任意の式
の場合、false
が返され、任意の式
は評価されません。
ReactのJSXでは、true
や false
は何もレンダリングされないため、この特性を利用して、hasNewMessage
が true
の場合のみ、div
要素(「🔔 新しいメッセージがあります」のアラート)がレンダリングされます。hasNewMessage
が false
の場合は、何も表示されません。
リストのレンダリング
リストのレンダリングは、配列データを繰り返し表示する機能です。Reactでは、JavaScriptのmap()
関数を使って配列を変換し、各要素に対応するJSXを生成します。
基本的なリストの表示
function TodoList() {
const todos = [
{ id: 1, text: "買い物に行く", completed: false },
{ id: 2, text: "React を学習する", completed: true },
{ id: 3, text: "プロジェクトを完成させる", completed: false },
{ id: 4, text: "友達と映画を見る", completed: false },
];
return (
<div>
<h3>今日のTODO</h3>
<ul>
{todos.map(todo => (
<li
key={todo.id}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
color: todo.completed ? '#888' : '#333',
}}
>
{todo.completed ? '✅' : '⏳'} {todo.text}
</li>
))}
</ul>
</div>
);
}
map()
メソッドを使ったリストのレンダリング{todos.map(todo => (...))}
の部分が、Reactでリストを表示する際の最も標準的な手法です。map()
メソッドはJavaScriptの配列メソッドで、配列の各要素に対して指定された関数を実行し、その結果を新しい配列として返します。- ここでは、
todos
配列の各todo
オブジェクトに対して、対応する<li>
要素(リストアイテム)を生成しています。生成された<li>
要素の配列がJSXの<ul>
タグ内に埋め込まれることで、リストとして表示されます。
key
プロップス (key={todo.id}
)- Reactでリストをレンダリングする際には、各リストアイテム(この場合は
<li>
)に一意のkey
プロップスを必ず設定する必要があります。 key
は、Reactがリストの各要素を識別し、リストの要素が追加、削除、または順序変更された際に、どのアイテムが変更されたかを効率的に追跡するために使われます。これにより、パフォーマンスが向上し、予期せぬ挙動を防ぐことができます。- この例では、各TODOオブジェクトの
id
プロパティがユニークなので、それをkey
として利用しています。
- Reactでリストをレンダリングする際には、各リストアイテム(この場合は
複雑なリストアイテムの表示
function ProductList() {
const products = [
{ id: 1, name: "iPhone 14", price: 119800, category: "スマートフォン", inStock: true },
{ id: 2, name: "iPad Air", price: 84800, category: "タブレット", inStock: true },
{ id: 3, name: "MacBook Pro", price: 248000, category: "ノートPC", inStock: false },
{ id: 4, name: "Apple Watch", price: 59800, category: "ウェアラブル", inStock: true },
];
return (
<div>
<h3>商品一覧</h3>
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<h4>{product.name}</h4>
<p>カテゴリ: {product.category}</p>
<p>価格: ¥{product.price.toLocaleString()}</p>
<p>
在庫:{" "}
{product.inStock ? (
<span style={{ color: 'green' }}>あり</span>
) : (
<span style={{ color: 'red' }}>なし</span>
)}
</p>
</div>
))}
</div>
</div>
);
}
map()
メソッドと条件付きレンダリングを組み合わせて、データのリストから動的なUIを生成する方法を示しています。
map()
メソッドによるリストのレンダリング{products.map(product => (...) )}
の部分が、Reactで動的なリストを生成しています。products
配列の各product
オブジェクトに対して関数を実行し、それぞれのproduct
データから<div className="product-card">
というJSX要素を生成しています。map()
メソッドはこれらのproduct-card
要素の配列を返し、それが<div className="product-grid">
の中に挿入されることで、複数の商品カードが一覧として表示されます。
key
プロップス (key={product.id}
)- リスト内の各要素には、Reactが要素を効率的に識別し追跡できるように、一意の
key
プロップスが必要です。ここでは、各商品オブジェクトのユニークなid
プロパティがkey
として利用されています。
- リスト内の各要素には、Reactが要素を効率的に識別し追跡できるように、一意の
イベントハンドリング
イベントハンドリングは、ユーザーの操作(クリック、入力など)に応答する機能です。Reactでは、関数をイベントハンドラーとして定義し、JSXの要素に割り当てます。
基本的なイベントハンドリング
function InteractiveButton() {
const handleClick = () => {
alert('ボタンがクリックされました!');
};
const handleMouseEnter = () => {
console.log('マウスが要素に入りました');
};
const handleMouseLeave = () => {
console.log('マウスが要素から出ました');
};
return (
<div>
<button
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
クリックしてください
</button>
</div>
);
}
ユーザーがウェブページ上の要素(この場合はボタン)とインタラクトしたときに、特定のJavaScript関数を実行します。
- イベントハンドラ関数の定義
handleClick
,handleMouseEnter
,handleMouseLeave
の3つの関数は、それぞれ特定のイベントが発生したときに実行されるロジック(イベントハンドラ)を定義しています。- これらの関数は、コンポーネントの内部で定義されており、必要に応じてボタンのイベント属性に割り当てられます。
- アロー関数 (
() => { ... }
) を使用することで、関数のコンテキスト(this
)の問題を気にすることなく簡潔に記述できます。
- JSXのイベント属性とハンドラ関数の紐付け:
<button>
タグの中に、onClick
,onMouseEnter
,onMouseLeave
といった属性が記述されています。これらはReactにおけるイベント属性です。- HTMLのイベント属性(例:
onclick="myFunction()"
)とは異なり、Reactのイベント属性はキャメルケースで記述されます(例:onClick
)。 - これらの属性には、直接JavaScriptのコード(通常は定義済みの関数)を波括弧
{}
で囲んで渡します。これにより、特定のイベントが発生した際に、対応するJavaScript関数が呼び出されます。
フォーム入力のハンドリング
function ContactForm() {
const handleSubmit = (event) => {
event.preventDefault(); // (1) デフォルトのフォーム送信動作をキャンセル
const formData = new FormData(event.target); // (2) フォームデータを取得
const name = formData.get('name');
const email = formData.get('email');
const message = formData.get('message');
alert(`お問い合わせありがとうございます!\n名前: ${name}\nメール: ${email}`);
// (3) ここで通常はAPIへのデータ送信などの処理を行う
};
const handleInputChange = (event) => {
// (4) 入力フィールドの値が変更されるたびにコンソールにログを出力
console.log(`${event.target.name}: ${event.target.value}`);
};
return (
<form onSubmit={handleSubmit}> {/* (5) フォームの送信イベントをハンドル */}
<h3>お問い合わせフォーム</h3>
<div>
<label>お名前:</label>
<input
type="text"
name="name"
onChange={handleInputChange} // (6) 入力変更イベントをハンドル
required // (7) 必須フィールド
/>
</div>
<div>
<label>メールアドレス:</label>
<input
type="email"
name="email"
onChange={handleInputChange}
required
/>
</div>
<div>
<label>メッセージ:</label>
<textarea
name="message"
onChange={handleInputChange}
required
></textarea>
</div>
<button type="submit">送信</button> {/* (8) フォーム送信ボタン */}
</form>
);
}
ユーザーがフォームに入力したデータを受け取り、それを処理する一連の仕組です。
- フォーム送信のハンドリング (
handleSubmit
)const handleSubmit = (event) => { ... };
がフォームが送信されたときに呼び出される関数を定義しています。event.preventDefault();
は非常に重要です。HTMLの通常のフォーム送信動作は、ページをリロードしたり、指定されたアクションURLにデータを送信したりします。Reactアプリケーションでは通常、ページの再読み込みを防ぎ、JavaScriptでデータを非同期に処理したいため、このメソッドを呼び出してデフォルトの動作をキャンセルします。const formData = new FormData(event.target);
はフォームの送信イベントから、event.target
(この場合は<form>
要素自身) をFormData
コンストラクタに渡すことで、フォーム内のすべての入力フィールドのデータを簡単に取得できます。formData.get('name')
は各<input>
や<textarea>
タグに設定されたname
属性(例:name="name"
)を使って、対応する入力値を取得します。- アラート表示 では取得した
name
とemail
の値をalert()
で表示していますが、実際のアプリケーションでは、ここで取得したデータをサーバーのAPIエンドポイントに送信したり、他の状態管理ロジックに渡したりする処理が行われます。
- 入力フィールドの変更ハンドリング (
handleInputChange
)const handleInputChange = (event) => { ... };
は各入力フィールドの値が変更されるたびに呼び出される関数を定義しています。
- JSXとイベント属性の紐付け
<form onSubmit={handleSubmit}>
では<form>
要素にはonSubmit
というReactのイベント属性があり、フォームが送信されたときにhandleSubmit
関数が呼び出されるように設定されています。<input onChange={handleInputChange}>
の<input>
や<textarea>
などの入力要素にはonChange
というイベント属性があり、ユーザーが入力値を変更するたびにhandleInputChange
関数が呼び出されるように設定されています。これにより、リアルタイムで入力値の変化を検出できます。
State管理
State(状態)は、コンポーネントが「覚えておく」データです。useStateフックを使用して、動的に変化するデータを管理できます。
ReactのuseState
フックは、関数コンポーネントに「状態」を持たせるための特別な機能です。これにより、コンポーネント内で変化するデータを管理し、そのデータが変更されたときに自動的にUIを更新できるようになります。
基本的なState管理
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
const reset = () => {
setCount(0);
};
return (
<div>
<h3>カウンター: {count}</h3>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={reset}>リセット</button>
</div>
);
}
ユーザーの操作に応じてUI上のデータを動的に更新し、それを画面に反映させる方法です。
- State変数の宣言
const [count, setCount] = useState(0);
この行がuseState
フックの最も重要な部分です。useState(0)
のuseState
を呼び出すと、State変数が作られます。引数に渡した0
が、このStateの初期値になります。[count, setCount]
はuseState
は配列を返します。配列の分割代入を使って、以下の2つの要素を取り出しています。count
は現在のStateの値を保持する変数です。UIに表示したり、計算に使ったりします。setCount
はcount
の値を更新するためだけの関数です。Stateの値を変更したいときは、必ずこのsetCount
関数を使います。
- Stateの更新とUIの自動再レンダリング
setCount(count + 1);
increment
、decrement
、reset
といった関数の中で、setCount
を使ってcount
の値を更新しています。
- JSXでのStateの表示とイベントハンドラ
<h3>カウンター: {count}</h3>
はJSXの中で波括弧{}
を使って、State変数count
の現在の値を直接表示しています。Stateが更新されると、この表示も自動で変わります。<button onClick={increment}>+1</button>
: ボタンのonClick
イベントに、先ほど定義したincrement
関数を渡しています。ユーザーがボタンをクリックするとincrement
が実行され、setCount
を通じてStateが更新され、最終的に画面が再レンダリングされるという一連の流れが生まれます。
複雑なStateの管理
import { useState } from 'react';
function UserSettings() {
const [settings, setSettings] = useState({
theme: 'light',
language: 'ja',
notifications: true,
fontSize: 14,
});
const handleThemeChange = (newTheme) => {
setSettings(prev => ({
...prev,
theme: newTheme
}));
};
const handleNotificationToggle = () => {
setSettings(prev => ({
...prev,
notifications: !prev.notifications
}));
};
const handleFontSizeChange = (event) => {
setSettings(prev => ({
...prev,
fontSize: parseInt(event.target.value)
}));
};
return (
<div>
<h3>設定</h3>
<div>
<label>テーマ: </label>
<select value={settings.theme} onChange={(e) => handleThemeChange(e.target.value)}>
<option value="light">ライト</option>
<option value="dark">ダーク</option>
</select>
</div>
<div>
<label>
<input
type="checkbox"
checked={settings.notifications}
onChange={handleNotificationToggle}
/>
通知を受け取る
</label>
</div>
<div>
<label>文字サイズ: {settings.fontSize}px</label>
<input
type="range"
min="12"
max="24"
value={settings.fontSize}
onChange={handleFontSizeChange}
/>
</div>
</div>
);
}
- オブジェクト形式のState
const [settings, setSettings] = useState({ ... });
ここでは、ユーザー設定の複数の項目(theme
、language
、notifications
、fontSize
)を一つのJavaScriptオブジェクトとしてStateに格納しています。このように関連するデータをまとめることで、Stateの管理がしやすくなります。
- オブジェクトStateの更新方法 (スプレッド構文
...prev
の利用)setSettings(prev => ({ ...prev, theme: newTheme }));
オブジェクト形式のStateを更新する際、Stateの一部だけを変更する場合でも、新しいオブジェクトを完全に作成して返す必要があります。ReactのStateは不変(immutable)であり、既存のオブジェクトを直接変更してはいけません。prev
: これはsetSettings
に渡される関数の中で利用できる引数で、更新前のStateの最新の値を指します。...prev
(スプレッド構文)はJavaScriptの記法で、prev
オブジェクトのすべてのプロパティと値を展開します。ここではこの構文を使って新しいオブジェクトにコピーします。theme: newTheme
では更新したいプロパティ(ここではtheme
)を新しい値で上書きします。 この方法により、language
やnotifications
など、変更しない他の設定プロパティはそのまま保持されつつ、theme
だけが更新されるという効率的かつ安全なState更新が実現されます。
- 各種入力要素とStateのバインディング
<select>
(ドロップダウンリスト)value={settings.theme}
はselect
要素のvalue
属性にStateの値(settings.theme
)を設定することで、表示されている選択肢が現在のStateと同期します。onChange={(e) => handleThemeChange(e.target.value)}
はユーザーが選択肢を変更するとonChange
イベントが発生し、そのイベントオブジェクトから新しい選択値(e.target.value
)を取得してhandleThemeChange
関数に渡しています。
<input type="checkbox">
(チェックボックス)checked={settings.notifications}
はチェックボックスのchecked
属性にStateの真偽値(settings.notifications
)を設定することで、チェック状態がStateと同期します。onChange={handleNotificationToggle}
はユーザーがチェックボックスを操作するとonChange
イベントが発生し、handleNotificationToggle
が呼び出されます。この関数内で!prev.notifications
を使ってStateの真偽値を反転させています。
<input type="range">
(スライダー):value={settings.fontSize}
はスライダーの現在値がStateのsettings.fontSize
と同期します。onChange={handleFontSizeChange}
はスライダーが動かされるとonChange
イベントが発生します。event.target.value
は常に文字列なので、parseInt()
を使って数値に変換してからStateを更新しています。
フックの使用
フックは、関数コンポーネントでReactの機能を「フック」するための特殊な関数です。useState以外にも、様々なフックが用意されています。
useEffectフックの活用
useEffect
は、関数コンポーネントで副作用(Side Effects) を実行するためのReactフックです。副作用とは、データ取得、DOMの直接操作、タイマーの設定(setInterval
など)、イベントリスナーの登録・解除など、Reactのレンダリングとは独立して行われる処理のことです。 useEffect(() => { /* 副作用の処理 */ }, [依存配列]);
の形式で使います。
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
const [isActive, setIsActive] = useState(false);
useEffect(() => {
let interval = null;
if (isActive) {
interval = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);
} else if (!isActive && seconds !== 0) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [isActive, seconds]);
const toggle = () => {
setIsActive(!isActive);
};
const reset = () => {
setSeconds(0);
setIsActive(false);
};
return (
<div>
<h3>ストップウォッチ</h3>
<div>経過時間: {seconds}秒</div>
<button onClick={toggle}>
{isActive ? '一時停止' : 'スタート'}
</button>
<button onClick={reset}>リセット</button>
</div>
);
}
useState
によるState管理const [seconds, setSeconds] = useState(0);
は経過時間を管理するStateです。const [isActive, setIsActive] = useState(false);
ではタイマーが現在動作中であるか(スタートしているか)を管理する真偽値のState。 これらのStateが変更されると、コンポーネントは再レンダリングされ、UIが更新されます。
- イベントハンドラ
toggle()
ではisActive
Stateを切り替えることで、useEffect
が再評価され、タイマーの開始/停止が制御されます。reset()
はseconds
を0に、isActive
をfalse
に設定し、タイマーを初期状態に戻します。setIsActive(false)
が呼ばれることでuseEffect
が再実行され、既存のタイマーが停止されます。
カスタムフックの作成
このコードはReactのカスタムフック(Custom Hook) の概念と、その具体的な活用例を示しています。useLocalStorage
というカスタムフックを作成し、これをNoteApp
コンポーネントで使うことで、ブラウザのlocalStorage
を使ってデータを永続化するメモアプリを実装しています。
import { useState, useEffect } from 'react'; // useEffectも使用するためインポートが必要です
// カスタムフック: useLocalStorage
function useLocalStorage(key, initialValue) {
// Stateを初期化する際にlocalStorageから値を読み込む
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
// JSON形式で保存されているため、パースして返す
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// エラーが発生した場合は初期値を返す
console.error("Error reading from localStorage:", error); // エラーハンドリングを追加
return initialValue;
}
});
// setValue関数はStateを更新し、同時にlocalStorageにも保存する
const setValue = (value) => {
try {
// 関数が渡された場合はその関数を実行して新しい値を取得
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
// 値をJSON文字列に変換してlocalStorageに保存
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error('Error saving to localStorage:', error);
}
};
return [storedValue, setValue];
}
// カスタムフックを使用するコンポーネント
function NoteApp() {
// useLocalStorageフックを使ってnotesの状態と、それを更新する関数を取得
const [notes, setNotes] = useLocalStorage('notes', []);
// 新しいメモ入力用のState
const [inputText, setInputText] = useState('');
// メモを追加する関数
const addNote = () => {
if (inputText.trim()) { // 入力が空でないかチェック
setNotes([...notes, { id: Date.now(), text: inputText, timestamp: new Date().toLocaleString() }]);
setInputText(''); // 入力フィールドをクリア
}
};
// メモを削除する関数
const deleteNote = (id) => {
setNotes(notes.filter(note => note.id !== id)); // 選択されたID以外のメモでフィルタリング
};
return (
<div>
<h3>メモアプリ</h3>
<div>
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="メモを入力..."
/>
<button onClick={addNote}>追加</button>
</div>
<ul>
{notes.map(note => (
<li key={note.id}>
<span>{note.text}</span>
<small> ({note.timestamp})</small>
<button onClick={() => deleteNote(note.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
useLocalStorage
カスタムフックの解説
useLocalStorage
フックは、ReactのStateをlocalStorage
に自動的に同期させる機能を提供します。
- Stateの遅延初期化と
localStorage
からの読み込みuseState(() => { ... });
のように、useState
に関数を渡すことで、Stateの初期化がコンポーネントの初回レンダリング時にのみ実行されるようになります。ここでは、その関数内でwindow.localStorage.getItem(key)
を使って、指定されたkey
に対応する値をlocalStorage
から読み込んでいます。
- State更新と
localStorage
への書き込みの同期- このカスタムフックの重要な機能は、
setStoredValue
(useState
が返すState更新関数)が呼び出されるたびに、その新しい値をlocalStorage
にも保存できることです。
- このカスタムフックの重要な機能は、
setValue
関数を独自に定義し、その中でsetStoredValue(valueToStore)
でReactのStateを更新しつつ、window.localStorage.setItem(key, JSON.stringify(valueToStore))
でlocalStorage
にも保存しています。- 関数形式の
setValue
のサポートをするためvalue instanceof Function ? value(storedValue) : value;
の部分で、setValue
に渡される引数が関数であるか(例:setCount(prev => prev + 1)
のように)をチェックし、その場合は現在のstoredValue
を引数にその関数を実行して、適切な新しい値を取得しています。これにより、useState
のset
関数が持つ機能(関数形式での更新)をそのまま利用できます。
- 関数形式の
useState
と同じAPIを返すreturn [storedValue, setValue];
のように、useState
フックと同じ[現在の値, 更新関数]
という形式で値を返しています。これにより、このカスタムフックを使用する側は、useState
と同じ感覚で扱えます。
NoteApp
コンポーネントでの useLocalStorage
の活用
NoteApp
コンポーネントは、作成したuseLocalStorage
フックを実際に利用する例です。
- カスタムフックの利用
const [notes, setNotes] = useLocalStorage('notes', []);
この一行で、notes
というState変数を宣言し、その初期値をlocalStorage
から読み込み、setNotes
関数が呼び出されるたびにnotes
の値を自動的にlocalStorage
に保存する機能を手に入れています。コンポーネントのコードは非常にシンプルに保たれています。
- メモの追加と削除
addNote
: 新しいメモをnotes
配列に追加し、入力フィールドをクリアします。setNotes
を呼ぶことで、useLocalStorage
フックが検知し、localStorage
も更新されます。deleteNote
: 指定されたid
を持つメモを配列からフィルタリングして削除します。こちらもsetNotes
を呼ぶことでlocalStorage
が更新されます。
コンポーネント間でのデータ共有
コンポーネント間でデータを共有する方法には、Props(プロパティ)の受け渡しや、State のリフトアップなどがあります。
Propsを使用したデータ共有
Reactアプリケーションで最も基本的なデータ共有の方法である「Props(プロップス)を使用したデータの一方向フロー」を示します。親コンポーネントがStateを管理し、そのStateやStateを更新する関数を子コンポーネントにPropsとして渡すことで、コンポーネント間の連携を実現しています。
import React, { useState } from 'react'; // ReactとuseStateをインポート
// 親コンポーネント
function ShoppingApp() {
const [cart, setCart] = useState([]); // カートの状態を管理
// カートに商品を追加する関数
const addToCart = (product) => {
setCart(prev => [...prev, product]); // 既存のカートに商品を追加
};
// カートから商品を削除する関数
const removeFromCart = (productId) => {
setCart(prev => prev.filter(item => item.id !== productId)); // 指定されたIDの商品をフィルタリングして削除
};
return (
<div>
<h2>オンラインショップ</h2>
{/* ProductListにaddToCart関数をプロップスとして渡す */}
<ProductList onAddToCart={addToCart} />
{/* ShoppingCartにカートのアイテムとremoveFromCart関数をプロップスとして渡す */}
<ShoppingCart items={cart} onRemoveFromCart={removeFromCart} />
</div>
);
}
// 商品リストコンポーネント
function ProductList({ onAddToCart }) { // 親から渡されたonAddToCartプロップスを受け取る
const products = [
{ id: 1, name: "コーヒー", price: 500 },
{ id: 2, name: "紅茶", price: 400 },
{ id: 3, name: "ジュース", price: 300 }
];
return (
<div>
<h3>商品一覧</h3>
{products.map(product => (
<div key={product.id}>
<span>{product.name} - ¥{product.price}</span>
{/* ボタンクリック時にonAddToCart関数を呼び出し、商品情報を渡す */}
<button onClick={() => onAddToCart(product)}>
カートに追加
</button>
</div>
))}
</div>
);
}
// ショッピングカートコンポーネント
function ShoppingCart({ items, onRemoveFromCart }) { // 親から渡されたitemsとonRemoveFromCartプロップスを受け取る
// カート内の商品の合計金額を計算
const total = items.reduce((sum, item) => sum + item.price, 0);
return (
<div>
<h3>ショッピングカート</h3>
{items.length === 0 ? ( // カートが空かどうかで表示を切り替える
<p>カートは空です</p>
) : (
<> {/* 複数の要素を返すためのFragment */}
{items.map((item, index) => ( // カート内のアイテムをリスト表示
<div key={index}> {/* リストのkeyはここではindexを使用 (注: 実際のアプリでは一意なID推奨) */}
<span>{item.name} - ¥{item.price}</span>
{/* ボタンクリック時にonRemoveFromCart関数を呼び出し、商品のIDを渡す */}
<button onClick={() => onRemoveFromCart(item.id)}>
削除
</button>
</div>
))}
<div>合計: ¥{total}</div> {/* 合計金額を表示 */}
</>
)}
</div>
);
}
このアプリケーションは、以下の3つのコンポーネントで構成されています。
ShoppingApp
(親コンポーネント)- アプリケーション全体のState(カートの中身)を管理します。
ProductList
(子コンポーネント)- 表示する商品データのリストを持っています。
ShoppingCart
(子コンポーネント)- 親から渡された「カート内のアイテムリスト」と「カートから削除する関数」をPropsとして受け取ります。
- 単一の真実の情報源 (Single Source of Truth)
ShoppingApp
コンポーネントが、cart
というStateを管理する唯一の場所(真実の情報源)です。カートの中身に関する情報はすべてこの親コンポーネントに集約されます。- このように、共有されるStateを最も近い共通の親コンポーネントで管理するのがReactの基本的な思想です。
- Propsを通じたデータの一方向フロー
- 親コンポーネント
ShoppingApp
のStateであるcart
は、items={cart}
というPropsとしてShoppingCart
コンポーネントに「下向き」に渡されます。 ShoppingApp
で定義されたaddToCart
関数とremoveFromCart
関数は、それぞれProductList
とShoppingCart
コンポーネントにPropsとして「下向き」に渡されます。- 子コンポーネントは受け取った関数を、ユーザーの操作(ボタンクリックなど)があったときに実行します。このとき、子コンポーネントは必要に応じて引数(例:
product
、productId
)を渡します。これにより、子コンポーネントは親コンポーネントのStateを変更するよう「通知」できます。
- 親コンポーネント
- Stateの不変性 (Immutability)
setCart(prev => [...prev, product])
やsetCart(prev => prev.filter(...))
のように、setCart
を呼び出す際には、既存のcart
配列を直接変更するのではなく、新しい配列を作成して返しています。これはReactのState更新の基本的なルールであり、変更を効率的に検知して再レンダリングを最適化するために重要です。
key
プロップスの重要性products.map(product => (<div key={product.id}>...</div>))
のように、リストをレンダリングする際には、各アイテムに一意なkey
プロップスを与えることが必須です。これによりReactはリストのアイテムを効率的に識別し、パフォーマンスを向上させることができます。ShoppingCart
では簡略化のためindex
を使っていますが、これはアイテムが追加・削除・並べ替えされる可能性がある場合には非推奨です。
Context APIを使用した深いデータ共有
ReactのContext APIを使って、コンポーネントツリーの階層を深く掘り下げずに(Propsをバケツリレーのように渡し続けることなく)、データを共有する方法を示します。
import { createContext, useContext, useState } from 'react';
// (1) テーマ用のContextを作成
const ThemeContext = createContext();
// (2) テーマプロバイダーコンポーネント
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light'); // (A) テーマの状態を管理
// (B) テーマを切り替える関数
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
// (C) Context.Provider で value を提供
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children} {/* (D) Providerでラップされた子コンポーネントがここにレンダリングされる */}
</ThemeContext.Provider>
);
}
// (3) テーマを使用するコンポーネント (Header)
function Header() {
// (E) useContextフックを使ってContextから値を取得
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<header
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff',
padding: '20px'
}}
>
<h1>My App</h1>
<button onClick={toggleTheme}> {/* (F) Contextから取得したtoggleTheme関数を使用 */}
{theme === 'light' ? 'ダークモード' : 'ライトモード'}
</button>
</header>
);
}
// (4) テーマを使用するコンポーネント (Content)
function Content() {
// (G) useContextフックを使ってContextから値を取得
const { theme } = useContext(ThemeContext);
return (
<main
style={{
background: theme === 'light' ? '#f5f5f5' : '#666',
color: theme === 'light' ? '#333' : '#fff',
padding: '20px'
}}
>
<p>現在のテーマ: {theme}</p>
</main>
);
}
// (5) アプリケーションのルート
function App() {
return (
// (H) ThemeProvider で Header と Content をラップする
<ThemeProvider>
<Header />
<Content />
</ThemeProvider>
);
}
createContext()
でContextを作成するconst ThemeContext = createContext();
これは、データを「提供」したり「消費」したりするための特別なJavaScriptオブジェクトを作成します。createContext()
の引数には、Contextの初期値を設定できますが、通常はnull
や空のオブジェクトで構いません(Providerが必ず値を設定するため)。
Provider
コンポーネント (ThemeContext.Provider
)function ThemeProvider({ children }) { ... }
このコンポーネントは、Contextの「供給元」となります。ThemeContext.Provider
は作成したContextオブジェクト(ThemeContext
)には、.Provider
というプロパティがあります。これは、Contextの値を設定するためのコンポーネントです。value={{ theme, toggleTheme }}
はProvider
のvalue
プロップスに、提供したいデータを渡します。ここでは、現在のテーマの状態(theme
)とテーマを切り替える関数(toggleTheme
)をオブジェクトとして渡しています。{children}
はThemeProvider
でラップされた子コンポーネントが、この{children}
の位置にレンダリングされます。
useContext()
フックでContextの値を消費するconst { theme, toggleTheme } = useContext(ThemeContext);
Header
コンポーネントとContent
コンポーネントでは、useContext
フックを使ってContextの値を取得しています。useContext(ThemeContext)
は引数に作成したContextオブジェクト(ThemeContext
)を渡すことで、最も近い上位のThemeContext.Provider
から提供されたvalue
にアクセスできます。- これにより、
Header
やContent
といったコンポーネントは、Props Drillingを行うことなく、必要なデータ(theme
)や関数(toggleTheme
)を直接利用できます。
- コンポーネントツリーにおけるProviderの位置
App
コンポーネント内で、<ThemeProvider>
が<Header>
と<Content>
をラップしている点に注目してください。Contextの恩恵を受けるすべてのコンポーネントは、そのContextのProvider
の子孫である必要があります。つまり、Header
とContent
はThemeProvider
の子孫なので、ThemeContext
の値にアクセスできるのです。
よくある質問(FAQ)
Q1. React 入門に必要な前提知識は何ですか?
A: React 学習を始める前に以下の知識が必要です
- HTML/CSS の基礎: マークアップとスタイリングの理解
- JavaScript の基礎: 変数、関数、オブジェクト、配列の操作
- ES6+ の構文: アロー関数、分割代入、テンプレートリテラルなど
Q2. React コンポーネントを作る際の命名規則はありますか?
A: React コンポーネントの命名には以下のルールがあります
- 必ず大文字で始める(PascalCase)
- 分かりやすい名前をつける
Q3. useState と props の違いがわかりません
A: React useState と props の主な違いは以下の通りです
特徴 | useState(state) | props |
---|---|---|
データの所有者 | そのコンポーネント | 親コンポーネント |
変更可能性 | 変更可能 | 読み取り専用 |
用途 | 内部状態の管理 | コンポーネント間のデータ受け渡し |
Q4. リストをレンダーする際に key が必要な理由は?
A: key は React がリストアイテムを効率的に管理するために必要です。アイテムの挿入、削除、並べ替えが発生した場合に、React は key を使って何が変更されたかを把握し、必要最小限の DOM 更新を行います。
まとめと次のステップ
この記事では、React の基本概念から実践的な使い方まで、幅広く解説しました。これらの知識を身につけることで、モダンなWebアプリケーションを構築する基礎が整います。
学習した内容の振り返り
- コンポーネント:UIを再利用可能な部品として管理
- JSX:JavaScriptの中でHTMLライクな記法を使用
- Props:コンポーネント間でデータを受け渡し
- State:コンポーネント内で動的なデータを管理
- イベントハンドリング:ユーザーの操作に応答
- フック:関数コンポーネントでReactの機能を活用
- 条件分岐とリスト:動的なUI表示を実現
次のステップとして学習すべき内容
- React Router:シングルページアプリケーション(SPA)のルーティング
- 状態管理ライブラリ:Redux、Zustand、Jotaiなど
- APIとの連携:fetch、axios、React Query、SWRなど
- テスト:Jest、React Testing Library
- パフォーマンス最適化:memo、useMemo、useCallback
- TypeScript:型安全なReact開発
- Next.js:Reactベースのフレームワーク
実践的な学習アドバイス
理論だけでなく、実際に手を動かしてアプリケーションを作成することが重要です。簡単なプロジェクトから始めて、徐々に複雑な機能を追加していくことで、Reactの理解が深まります。
また、React の公式ドキュメントやコミュニティの情報を積極的に活用し、最新の開発トレンドやベストプラクティスを学び続けることが、スキル向上の鍵となります。
最後に:React の学習は継続的なプロセスです。基礎をしっかりと身につけた上で、実際のプロジェクトに取り組み、エラーを解決しながら経験を積んでいくことが最も効果的な学習方法です。