Reactのreturn文の書き方|複数要素を返す時の注意点
Reactコンポーネントのreturn文で複数要素を返す方法を解説。React Fragment、div要素、配列など様々な方法と実際のコード例とともに詳しく説明します。
みなさん、Reactのreturn文で悩んだことはありませんか?
「複数の要素をreturnしたいけどエラーが出る」 「div要素で囲むのが正解?」 「React Fragmentって何だろう?」
こんな疑問を持ったことがある方も多いでしょう。
実は、Reactのreturn文には特別なルールがあります。 でも大丈夫です!この記事では、複数要素を返す3つの方法を実際のコード例とともに詳しく解説します。
読み終わる頃には、どの方法をいつ使えばいいかがしっかりと理解できるはずです。
Reactのreturn文で知っておくべき基本ルール
Reactを始めたばかりの時、誰もが通る道があります。 それは「複数要素のreturnでエラーが出る」という問題です。
単一要素なら問題なし
import React from 'react';
// これは問題なく動作します
function SimpleComponent() {
return <h1>こんにちは!</h1>;
}
// 複雑な構造でも、1つの要素で囲まれていればOK
function ComplexComponent() {
return (
<div className="container">
<h1>タイトル</h1>
<p>段落テキスト</p>
<button>クリック</button>
</div>
);
}
上記のコードでは、<h1>
要素や<div>
要素が1つのまとまりになっています。
これなら何の問題もありません。
複数要素だとエラーになる理由
// これはエラーになります
function ErrorComponent() {
return (
<h1>タイトル</h1>
<p>段落</p>
<button>ボタン</button>
);
// エラー: Adjacent JSX elements must be wrapped in an enclosing tag
}
なぜエラーになるのでしょうか?
実は、JSXは内部的にJavaScript関数に変換されるんです。
// JSXは内部的にこのように変換される
React.createElement('div', null,
React.createElement('h1', null, 'タイトル'),
React.createElement('p', null, '段落')
);
// 複数要素を直接returnしようとすると...
return React.createElement('h1', null, 'タイトル')
React.createElement('p', null, '段落'); // 構文エラー!
JavaScript的に考えると、return文で複数の値を同時に返すことはできませんよね。 Reactでは、1つのコンポーネントは必ず1つの要素を返す必要があるというルールがあるんです。
でも心配いりません!この問題を解決する方法が3つあります。
方法1: div要素で囲む(一番分かりやすい方法)
最初に覚える方法がこれです。
すべての要素を<div>
で包んでしまう方法ですね。
基本的な使い方
function DivWrapper() {
return (
<div>
<h1>ブログ記事タイトル</h1>
<p>投稿日: 2024年1月1日</p>
<p>記事の本文がここに入ります。</p>
<button>いいね</button>
</div>
);
}
このように<div>
で囲むだけで、複数の要素を1つにまとめることができます。
とても簡単ですよね!
CSSクラスを適用する場合
function StyledComponent() {
return (
<div className="article-container">
<header className="article-header">
<h1>記事タイトル</h1>
<p className="publish-date">2024年1月1日</p>
</header>
<main className="article-content">
<p>記事の本文です。</p>
<p>複数の段落があります。</p>
</main>
<footer className="article-footer">
<button className="like-button">いいね</button>
<button className="share-button">シェア</button>
</footer>
</div>
);
}
div
要素にCSSクラスを適用することで、スタイリングも自由自在です。
対応するCSSはこんな感じになります。
.article-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.article-header {
border-bottom: 1px solid #eee;
padding-bottom: 15px;
margin-bottom: 20px;
}
.article-content {
line-height: 1.6;
margin-bottom: 20px;
}
.article-footer {
display: flex;
gap: 10px;
}
div要素を使う時の注意点
ただし、div要素を使いすぎると問題が起こることもあります。
// 避けたい例:不要なdivが多すぎる
function TooManyDivs() {
return (
<div> {/* 外側のdiv */}
<div> {/* 不要なdiv */}
<div> {/* さらに不要なdiv */}
<h1>タイトル</h1>
<p>内容</p>
</div>
</div>
</div>
);
}
// 改善例:意味のあるdivのみ使用
function BetterStructure() {
return (
<article className="blog-post">
<h1>タイトル</h1>
<p>内容</p>
</article>
);
}
div要素は便利ですが、使いすぎるとHTMLが複雑になってしまいます。 本当に必要な場合だけ使うことを心がけましょう。
方法2: React Fragment(推奨される方法)
React 16から登場した、とても便利な機能です。 余分なDOM要素を作らずに複数要素をまとめることができます。
React.Fragmentの基本的な使い方
import React from 'react';
function FragmentExample() {
return (
<React.Fragment>
<h1>React Fragment の例</h1>
<p>余分なDOM要素は作成されません</p>
<button>アクション</button>
</React.Fragment>
);
}
React.Fragment
で囲むことで、div要素を使わずに複数要素をまとめられます。
短縮構文でもっと簡単に
function ShortFragment() {
return (
<>
<h1>短縮構文の例</h1>
<p>より簡潔に書けます</p>
<button>クリック</button>
</>
);
}
<>...</>
という短縮構文を使えば、さらに簡潔に書けます。
この記号が分からない方も多いかもしれませんが、React Fragmentと全く同じ意味です。
Fragment vs div の違いを見てみよう
実際にDOMがどう変わるか比較してみましょう。
// div要素を使った場合
function WithDiv() {
return (
<div>
<h1>タイトル</h1>
<p>内容</p>
</div>
);
}
// 生成されるDOM:
// <div>
// <h1>タイトル</h1>
// <p>内容</p>
// </div>
// React Fragmentを使った場合
function WithFragment() {
return (
<>
<h1>タイトル</h1>
<p>内容</p>
</>
);
}
// 生成されるDOM:
// <h1>タイトル</h1>
// <p>内容</p>
Fragmentを使うと、不要なdiv要素が作られないのが分かりますね。
HTMLがよりシンプルになり、CSSのスタイリングも楽になります。
実際にFragmentが活躍する場面
ナビゲーションメニューを作る場合を考えてみましょう。
// ナビゲーションメニューの例
function NavigationMenu() {
return (
<>
<a href="/">ホーム</a>
<a href="/about">概要</a>
<a href="/contact">お問い合わせ</a>
</>
);
}
この場合、わざわざdiv要素で囲む必要はありませんよね。 Fragmentを使うことで、必要最小限のHTMLを生成できます。
テーブルの行を作る場合も同様です。
// テーブル行の例
function UserTableRow({ user }) {
return (
<>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.role}</td>
</>
);
}
// 使用例
function UserTable({ users }) {
return (
<table>
<thead>
<tr>
<th>名前</th>
<th>メール</th>
<th>役割</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<UserTableRow user={user} />
</tr>
))}
</tbody>
</table>
);
}
テーブルの構造を壊すことなく、きれいなHTMLを生成できます。
key属性が必要な場合の注意点
配列の中でFragmentを使う場合は、key属性が必要になります。
// 配列の中でFragmentを使用する場合
function ItemList({ items }) {
return (
<div>
{items.map(item => (
<React.Fragment key={item.id}>
<h3>{item.title}</h3>
<p>{item.description}</p>
<hr />
</React.Fragment>
))}
</div>
);
}
この場合はReact.Fragment
を使う必要があります。
短縮構文(<>...</>
)ではkey属性が使えないので注意してください。
// これはエラーになります
function IncorrectKeyUsage({ items }) {
return (
<div>
{items.map(item => (
<key={item.id}> {/* エラー: 短縮構文ではkey使用不可 */}
<h3>{item.title}</h3>
<p>{item.description}</p>
</>
))}
</div>
);
}
方法3: 配列として返す(特殊なケースで使用)
最後に、配列として複数要素を返す方法を紹介します。 あまり使う機会は多くありませんが、知っておくと便利です。
基本的な配列の書き方
function ArrayReturn() {
return [
<h1 key="title">配列で返す例</h1>,
<p key="content">複数の要素を配列として返せます</p>,
<button key="action">アクション</button>
];
}
配列の中にJSX要素を入れて返すという方法です。 ちょっと特殊ですが、これも有効な方法なんです。
重要なポイントは、各要素に必ずkey属性をつけることです。
動的に配列を生成する場合
function DynamicArrayReturn({ items }) {
const elements = [];
// ヘッダーを追加
elements.push(<h1 key="header">アイテム一覧</h1>);
// アイテムを動的に追加
items.forEach((item, index) => {
elements.push(
<div key={`item-${item.id}`}>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
);
// 最後以外は区切り線を追加
if (index < items.length - 1) {
elements.push(<hr key={`divider-${index}`} />);
}
});
// フッターを追加
elements.push(
<p key="footer">合計: {items.length}件</p>
);
return elements;
}
このように、プログラマティックに要素を組み立てたい場合に便利です。
条件によって要素を動的に追加したり、繰り返し処理で要素を生成したりできます。
mapを使った配列の生成
function BlogPostList({ posts }) {
// ヘッダー、投稿一覧、フッターを配列として返す
return [
<header key="header">
<h1>ブログ投稿</h1>
</header>,
...posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<time>{post.publishedAt}</time>
</article>
)),
<footer key="footer">
<p>合計 {posts.length} 件の投稿</p>
</footer>
];
}
スプレッド構文(...
)を使って、配列を展開することもできます。
配列使用時の重要な注意点
配列を使う場合は、必ず以下の点に注意してください。
// NG: key属性がない
function MissingKeys() {
return [
<h1>タイトル</h1>, // 警告: key属性がない
<p>内容</p>, // 警告: key属性がない
<button>ボタン</button> // 警告: key属性がない
];
}
// OK: 適切なkey属性
function ProperKeys() {
return [
<h1 key="title">タイトル</h1>,
<p key="content">内容</p>,
<button key="button">ボタン</button>
];
}
// NG: 重複するkey
function DuplicateKeys() {
return [
<p key="item">項目1</p>,
<p key="item">項目2</p>, // エラー: 重複するkey
];
}
key属性は、Reactが要素を正しく管理するために必要です。 必ず一意の値を設定するようにしましょう。
条件分岐でのreturn文の書き方
実際の開発では、条件によって異なる内容を表示することがよくあります。 そんな時のreturn文の書き方を見てみましょう。
基本的な条件分岐
function ConditionalReturn({ isLoggedIn, user }) {
if (isLoggedIn) {
return (
<>
<h1>ようこそ、{user.name}さん!</h1>
<p>最後のログイン: {user.lastLogin}</p>
<button>ログアウト</button>
</>
);
}
return (
<>
<h1>ログインしてください</h1>
<p>アカウントにアクセスするにはログインが必要です</p>
<button>ログイン</button>
</>
);
}
ログイン状態によって表示内容を変える、よくあるパターンですね。
ここでもFragmentを使って、シンプルな構造を保っています。
より複雑な条件分岐
function ComplexConditionalReturn({ userRole, isLoading, error }) {
// ローディング状態
if (isLoading) {
return (
<div className="loading">
<p>読み込み中...</p>
<div className="spinner"></div>
</div>
);
}
// エラー状態
if (error) {
return (
<div className="error">
<h2>エラーが発生しました</h2>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>
再読み込み
</button>
</div>
);
}
// ユーザーの役割に応じた表示
switch (userRole) {
case 'admin':
return (
<>
<h1>管理者ダッシュボード</h1>
<nav>
<a href="/admin/users">ユーザー管理</a>
<a href="/admin/settings">システム設定</a>
<a href="/admin/logs">ログ確認</a>
</nav>
<div className="admin-content">
<p>管理者専用機能へアクセスできます</p>
</div>
</>
);
case 'user':
return (
<>
<h1>ユーザーダッシュボード</h1>
<nav>
<a href="/profile">プロフィール</a>
<a href="/settings">設定</a>
</nav>
<div className="user-content">
<p>ユーザー機能をご利用いただけます</p>
</div>
</>
);
case 'guest':
return (
<>
<h1>ゲストユーザー</h1>
<p>限定的な機能のみご利用いただけます</p>
<button>アカウント作成</button>
</>
);
default:
return (
<>
<h1>不明なユーザー役割</h1>
<p>システム管理者にお問い合わせください</p>
</>
);
}
}
ローディング、エラー、ユーザー役割など、複数の条件を組み合わせた例です。
switch
文を使うことで、コードがより読みやすくなっています。
三項演算子を使った条件分岐
function TernaryConditionalReturn({ isPublished, post }) {
return (
<>
<h1>{post.title}</h1>
<p>作成者: {post.author}</p>
{isPublished ? (
<>
<p className="published">公開済み</p>
<time>公開日: {post.publishedAt}</time>
<div className="post-content">
{post.content}
</div>
<div className="post-actions">
<button>シェア</button>
<button>ブックマーク</button>
</div>
</>
) : (
<>
<p className="draft">下書き</p>
<time>最終更新: {post.updatedAt}</time>
<div className="draft-actions">
<button>編集</button>
<button>公開</button>
<button>削除</button>
</div>
</>
)}
<footer className="post-footer">
<p>カテゴリ: {post.category}</p>
<p>タグ: {post.tags.join(', ')}</p>
</footer>
</>
);
}
ブログ記事の公開状態によって、表示内容を切り替える例です。
三項演算子を使うことで、1つのreturn文の中で条件分岐ができています。
実際の開発でよく使うパターン
ここまでの知識を使って、実際の開発でよく使われるパターンを見てみましょう。
商品カードコンポーネント
function ProductCard({ product, showDetails, onAddToCart, onToggleDetails }) {
return (
<>
<div className="product-image">
<img src={product.image} alt={product.name} />
{product.sale && (
<span className="sale-badge">セール</span>
)}
</div>
<div className="product-info">
<h3>{product.name}</h3>
<p className="price">
{product.sale ? (
<>
<span className="original-price">
¥{product.originalPrice.toLocaleString()}
</span>
<span className="sale-price">
¥{product.price.toLocaleString()}
</span>
</>
) : (
<span>¥{product.price.toLocaleString()}</span>
)}
</p>
<div className="product-rating">
{[...Array(5)].map((_, index) => (
<span
key={index}
className={index < product.rating ? 'star filled' : 'star'}
>
★
</span>
))}
<span className="rating-count">({product.reviewCount})</span>
</div>
</div>
{showDetails && (
<>
<div className="product-details">
<p>{product.description}</p>
<ul className="features">
{product.features.map((feature, index) => (
<li key={index}>{feature}</li>
))}
</ul>
</div>
<div className="product-specs">
<h4>仕様</h4>
<dl>
{Object.entries(product.specifications).map(([key, value]) => (
<React.Fragment key={key}>
<dt>{key}</dt>
<dd>{value}</dd>
</React.Fragment>
))}
</dl>
</div>
</>
)}
<div className="product-actions">
<button onClick={() => onAddToCart(product.id)}>
カートに追加
</button>
<button onClick={() => onToggleDetails()}>
{showDetails ? '詳細を隠す' : '詳細を見る'}
</button>
</div>
</>
);
}
このコンポーネントは商品の情報を表示するカード型のUIです。
Fragmentを活用することで、余分なdiv要素を使わずに済んでいます。 条件分岐も自然に組み込まれていますね。
データテーブルコンポーネント
function DataTable({ data, loading, error, onSort, sortColumn, sortDirection }) {
if (loading) {
return (
<div className="data-table-loading">
<p>データを読み込み中...</p>
<div className="loading-spinner"></div>
</div>
);
}
if (error) {
return (
<div className="data-table-error">
<h3>エラーが発生しました</h3>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>
再読み込み
</button>
</div>
);
}
if (!data || data.length === 0) {
return (
<div className="data-table-empty">
<p>表示するデータがありません</p>
<button onClick={() => window.location.reload()}>
更新
</button>
</div>
);
}
return (
<>
<div className="data-table-header">
<h2>データ一覧</h2>
<div className="table-info">
<span>合計: {data.length}件</span>
</div>
</div>
<div className="data-table-container">
<table className="data-table">
<thead>
<tr>
{data.columns.map(column => (
<th
key={column.key}
onClick={() => column.sortable && onSort(column.key)}
className={`
${column.sortable ? 'sortable' : ''}
${sortColumn === column.key ? 'sorted' : ''}
${sortColumn === column.key ? sortDirection : ''}
`}
>
{column.label}
{column.sortable && sortColumn === column.key && (
<span className="sort-indicator">
{sortDirection === 'asc' ? '↑' : '↓'}
</span>
)}
</th>
))}
</tr>
</thead>
<tbody>
{data.rows.map((row, rowIndex) => (
<tr key={rowIndex}>
{data.columns.map(column => (
<td key={column.key}>
{row[column.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<div className="data-table-footer">
<p>最終更新: {new Date().toLocaleString()}</p>
</div>
</>
);
}
データテーブルは状態によって表示内容が大きく変わります。
ローディング、エラー、空データ、正常データの4パターンを適切に処理しています。 複雑なコンポーネントでも、return文のルールを守ることで読みやすいコードになります。
よくある間違いとその解決方法
return文でよくある間違いを見て、正しい書き方を覚えましょう。
1. returnの後の改行ミス
// NG: returnの直後に改行
function BadReturn() {
return
<div>
<h1>タイトル</h1>
</div>;
// エラー: undefined が返される
}
// OK: return文の正しい書き方
function GoodReturn() {
return (
<div>
<h1>タイトル</h1>
</div>
);
}
// OK: 括弧なしでも一行で書けば動作する
function OneLineReturn() {
return <div><h1>タイトル</h1></div>;
}
JavaScriptでは、return
の直後に改行があると、自動的にセミコロンが挿入されてしまいます。
必ず括弧で囲むか、一行で書くようにしましょう。
2. 条件分岐で何も返さない
// NG: undefinedが返される可能性
function BadConditional({ showContent }) {
if (showContent) {
return <div>コンテンツ</div>;
}
// return文がない場合、undefinedが返される
}
// OK: 必ず何かを返す
function GoodConditional({ showContent }) {
if (showContent) {
return <div>コンテンツ</div>;
}
return <div>コンテンツがありません</div>;
}
// OK: 三項演算子やFragmentを使用
function AlternativeConditional({ showContent }) {
return (
<>
{showContent ? (
<div>コンテンツ</div>
) : (
<div>コンテンツがありません</div>
)}
</>
);
}
条件分岐を使う場合は、すべてのパターンで何かを返すようにしましょう。
3. 配列のkey属性の間違い
// NG: key属性がない
function MissingKeys({ items }) {
return items.map(item => (
<div>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
));
}
// NG: インデックスをkeyに使用(推奨されない)
function IndexKeys({ items }) {
return items.map((item, index) => (
<div key={index}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
));
}
// OK: 適切なkey属性
function ProperKeys({ items }) {
return items.map(item => (
<div key={item.id}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
));
}
配列を使う場合は、必ず一意のkey属性を設定しましょう。 インデックスをkeyに使うのは、要素の順序が変わる可能性がある場合は避けた方が安全です。
4. Fragment使用時のkey属性
// NG: 短縮構文でkey属性を使用
function FragmentKeyError({ items }) {
return items.map(item => (
<key={item.id}> {/* エラー: 短縮構文ではkey使用不可 */}
<h3>{item.title}</h3>
<p>{item.content}</p>
</>
));
}
// OK: React.Fragmentでkey属性を使用
function FragmentKeyCorrect({ items }) {
return items.map(item => (
<React.Fragment key={item.id}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</React.Fragment>
));
}
短縮構文(<>...</>
)ではkey属性が使えません。
配列の中でFragmentを使う場合は、React.Fragment
を使いましょう。
パフォーマンスへの影響を理解しよう
different return methods のパフォーマンスについて簡単に解説します。
DOM構造の違い
div要素とFragmentでは、生成されるDOM構造が変わります。
// div要素を使用した場合
function WithDiv() {
return (
<div className="wrapper">
<h1>タイトル</h1>
<p>内容</p>
</div>
);
}
// 生成されるDOM:
// <div class="wrapper">
// <h1>タイトル</h1>
// <p>内容</p>
// </div>
// Fragmentを使用した場合
function WithFragment() {
return (
<>
<h1>タイトル</h1>
<p>内容</p>
</>
);
}
// 生成されるDOM:
// <h1>タイトル</h1>
// <p>内容</p>
Fragmentを使うことで、不要なDOM要素を減らせるため、メモリ使用量とレンダリング速度の向上が期待できます。
CSSセレクターへの影響
DOM構造が変わると、CSSセレクターの書き方も変わります。
/* div要素がある場合のCSS */
.wrapper h1 {
color: blue;
}
.wrapper p {
margin: 10px 0;
}
/* Fragmentの場合(直接の親子関係) */
.container > h1 {
color: blue;
}
.container > p {
margin: 10px 0;
}
Fragmentを使う場合は、親要素との関係を考えてCSSを書く必要があります。
メモリ効率の比較
// より多くのDOM要素(メモリ使用量が多い)
function MoreDOMNodes() {
return (
<div className="outer">
<div className="inner">
<div className="content">
<h1>タイトル</h1>
<p>内容</p>
</div>
</div>
</div>
);
}
// 必要最小限のDOM要素(メモリ効率が良い)
function MinimalDOMNodes() {
return (
<>
<h1>タイトル</h1>
<p>内容</p>
</>
);
}
大規模なアプリケーションでは、この差が積み重なって大きな影響を与える可能性があります。
まとめ
Reactのreturn文で複数要素を返す方法について詳しく解説しました。
3つの主な方法
- div要素で囲む - 分かりやすいが、余分なDOM要素が作られる
- React Fragment - 推奨方法、余分なDOM要素を作らない
- 配列として返す - 特殊なケースで有用、key属性が必要
覚えておきたいポイント
- Reactコンポーネントは必ず単一要素を返す
- React Fragment(
<>...</>
)の使用が推奨される - 配列で返す場合は必ずkey属性を指定する
- 条件分岐では必ず何かを返すようにする
- 短縮構文ではkey属性は使えない
適切な方法を選択することで、DOM構造を最適化し、保守性の高いコンポーネントを作成できます。
特にReact Fragmentを活用することで、無駄なDOM要素を減らし、パフォーマンスの向上も期待できますね。
ぜひ実際のプロジェクトで、これらの方法を使い分けてみてください。 きっと、より効率的で読みやすいReactコンポーネントが作れるようになるはずです!