ReactのPresentational ComponentとContainer Componentを完全理解!役割と使い分けを徹底解説
生徒
「ReactでPresentational ComponentとContainer Componentという言葉を聞いたんですけど、何が違うんですか?」
先生
「これは、コンポーネントの役割を分けて整理するための考え方です。それぞれに明確な役割があるんですよ。」
生徒
「役割を分けるって、具体的にどういうメリットがあるんですか?」
先生
「プログラムが整理されて、修正やテストがとても楽になります。それでは、詳しく見ていきましょう!」
1. Presentational ComponentとContainer Componentとは?
Reactでアプリケーションを作るとき、コンポーネントを二つの種類に分けて考える方法があります。それが、Presentational Component(プレゼンテーショナルコンポーネント)とContainer Component(コンテナコンポーネント)です。この考え方は、レストランの役割分担に例えることができます。
レストランでは、料理を作るシェフと、料理を運んで提供するホールスタッフがいますよね。シェフは調理に専念し、ホールスタッフは接客に専念します。Reactのコンポーネントも同じように、見た目を担当するコンポーネントと、データや処理を担当するコンポーネントに分けることで、それぞれの役割が明確になるのです。
Presentational Componentは、画面の見た目だけを担当するコンポーネントです。一方、Container Componentは、データの取得や状態の管理など、裏側の処理を担当します。この役割分担により、プログラムが読みやすく、管理しやすくなります。
2. Presentational Componentの特徴と役割
Presentational Componentは、見た目だけを担当するコンポーネントです。このコンポーネントは、データをどこから取得するのか、どう処理するのかは知りません。ただ、受け取ったデータをどう表示するかだけに集中します。
例えば、テレビのアナウンサーを想像してください。アナウンサーは、原稿を渡されたらそれを読み上げるだけで、原稿の内容を自分で考えたり調査したりはしません。Presentational Componentも同じで、親コンポーネントから渡されたpropsを受け取って、それを画面に表示するだけなのです。
Presentational Componentの特徴は、useStateやuseEffectなどの状態管理をほとんど使わないことです。すべてのデータはpropsとして受け取り、表示に専念します。これにより、同じコンポーネントを様々な場所で再利用しやすくなります。
3. Presentational Componentの実装例
それでは、実際にPresentational Componentを作ってみましょう。ユーザー情報を表示するシンプルなコンポーネントです。
import React from "react";
function UserProfile(props) {
return (
<div style={{
border: "2px solid #007bff",
padding: "20px",
borderRadius: "8px",
maxWidth: "300px"
}}>
<h3>{props.name}</h3>
<p>年齢: {props.age}歳</p>
<p>職業: {props.job}</p>
<p>メール: {props.email}</p>
</div>
);
}
export default UserProfile;
このコンポーネントには、データの取得や状態の管理は一切含まれていません。すべてpropsから受け取った値を表示しているだけです。このように作ることで、テストが簡単になり、どんなデータでも表示できる汎用的なコンポーネントになります。
4. Container Componentの特徴と役割
Container Componentは、データの管理と処理を担当するコンポーネントです。このコンポーネントは、APIからデータを取得したり、状態を管理したり、計算処理を行ったりします。そして、処理したデータをPresentational Componentに渡す役割を持ちます。
先ほどのレストランの例で言えば、Container Componentはシェフの役割です。材料を仕入れて、下ごしらえをして、料理を作ります。そして、完成した料理をホールスタッフ(Presentational Component)に渡すのです。
Container Componentの特徴は、useState、useEffect、useContextなどのReact Hooksを積極的に使うことです。また、外部のAPIと通信したり、複雑な計算処理を行ったりします。見た目については気にせず、データの準備と管理に専念します。
5. Container Componentの実装例
次に、Container Componentを作ってみましょう。ユーザー情報を管理して、先ほどのUserProfileコンポーネントにデータを渡す例です。
import React, { useState, useEffect } from "react";
import UserProfile from "./UserProfile";
function UserContainer() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 実際のアプリでは、ここでAPIからデータを取得します
setTimeout(() => {
const userData = {
name: "山田太郎",
age: 28,
job: "エンジニア",
email: "yamada@example.com"
};
setUser(userData);
setLoading(false);
}, 1000);
}, []);
if (loading) {
return <p>読み込み中...</p>;
}
return <UserProfile {...user} />;
}
export default UserContainer;
useEffectは、コンポーネントが画面に表示されたときに実行される処理を書く場所です。ここでは、データの取得を模擬しています。実際のアプリケーションでは、fetchやaxiosなどを使ってサーバーからデータを取得します。
6. 二つのコンポーネントを組み合わせる具体例
Presentational ComponentとContainer Componentを組み合わせた、より実践的な例を見てみましょう。商品リストを表示するアプリケーションです。
import React from "react";
// Presentational Component - 商品カードの見た目だけを担当
function ProductCard(props) {
return (
<div style={{
border: "1px solid #ddd",
padding: "15px",
margin: "10px",
borderRadius: "5px"
}}>
<h4>{props.name}</h4>
<p style={{ color: "#666" }}>{props.description}</p>
<p style={{ fontSize: "20px", fontWeight: "bold", color: "#28a745" }}>
¥{props.price.toLocaleString()}
</p>
<button onClick={() => props.onAddToCart(props.id)} style={{
backgroundColor: "#007bff",
color: "white",
padding: "8px 16px",
border: "none",
borderRadius: "4px",
cursor: "pointer"
}}>
カートに追加
</button>
</div>
);
}
export default ProductCard;
このように、見た目のコンポーネントは処理の詳細を知らなくても良いようにします。ボタンがクリックされたら、渡された関数を実行するだけです。これにより、同じ商品カードを様々な場所で使い回すことができます。
7. Container Componentでデータと処理を管理する
次に、商品リストを管理するContainer Componentを作ります。このコンポーネントが、データの準備とカート機能の実装を担当します。
import React, { useState } from "react";
import ProductCard from "./ProductCard";
function ProductListContainer() {
const [products] = useState([
{ id: 1, name: "ノートパソコン", description: "高性能で軽量", price: 89800 },
{ id: 2, name: "ワイヤレスマウス", description: "快適な操作性", price: 2980 },
{ id: 3, name: "キーボード", description: "静音タイプ", price: 5800 }
]);
const [cart, setCart] = useState([]);
const handleAddToCart = (productId) => {
const product = products.find(p => p.id === productId);
setCart([...cart, product]);
alert(`${product.name}をカートに追加しました!`);
};
return (
<div style={{ padding: "20px" }}>
<h1>商品一覧</h1>
<p>カートの商品数: {cart.length}個</p>
<div>
{products.map(product => (
<ProductCard
key={product.id}
{...product}
onAddToCart={handleAddToCart}
/>
))}
</div>
</div>
);
}
export default ProductListContainer;
このContainer Componentは、商品データの管理、カートの状態管理、カートに追加する処理など、すべての複雑な処理を担当しています。ProductCardコンポーネントは、これらの処理を知らなくても、渡されたデータを表示するだけで機能します。
8. コンポーネント分離のメリット
Presentational ComponentとContainer Componentを分けることで、様々なメリットが得られます。まず、コードの見通しが良くなります。見た目の部分と処理の部分が分かれているので、どこを修正すれば良いかすぐに分かります。デザインを変えたいときはPresentational Componentだけを、処理を変えたいときはContainer Componentだけを修正すれば良いのです。
また、再利用性が高まります。Presentational Componentは、どんなデータでも表示できる汎用的な作りになっているため、様々な場面で使い回せます。例えば、ユーザープロフィールの表示コンポーネントは、管理画面でも、マイページでも、検索結果でも使えるのです。
テストもしやすくなります。Presentational Componentは、特定のpropsを渡せば決まった表示になるため、自動テストが書きやすいです。Container Componentも、データ取得や状態管理のロジックだけに集中してテストできます。さらに、チーム開発の際には、デザイナーがPresentational Componentを、エンジニアがContainer Componentを担当するという分業もしやすくなります。
9. 現代のReactにおける考え方の変化
Presentational ComponentとContainer Componentという考え方は、React Hooksが登場する前によく使われていました。当時は、クラスコンポーネントという書き方が主流で、状態を持つコンポーネントと持たないコンポーネントを明確に分ける必要があったのです。
しかし、React Hooksの登場により、どのコンポーネントでも簡単に状態を持てるようになりました。そのため、厳密にPresentational ComponentとContainer Componentを分ける必要性は減ってきています。現代のReact開発では、もっと柔軟に考えることが多くなりました。
ただし、この考え方の本質である「見た目と処理を分離する」という原則は、今でも非常に重要です。カスタムフックという機能を使って処理をまとめたり、コンポーネントの責任を明確にしたりすることで、同じような効果が得られます。大切なのは、分類にこだわることではなく、コードを整理して読みやすくする意識を持つことです。
10. 実践的な使い分けのポイント
実際の開発では、すべてのコンポーネントを厳密にPresentational ComponentとContainer Componentに分ける必要はありません。小規模なアプリケーションでは、むしろ一つのコンポーネントにまとめた方がシンプルで分かりやすいこともあります。
コンポーネントを分けるべきタイミングは、以下のような場合です。まず、同じ見た目を複数の場所で使いたいとき。次に、コンポーネントが複雑になりすぎて読みにくくなったとき。そして、デザインと処理を別々の人が担当するとき。これらの状況では、コンポーネントを分離することで開発効率が上がります。
また、APIからデータを取得する処理や、複雑な状態管理が必要な場合は、Container Componentとして分離すると良いでしょう。一方、ボタンやカード、リストアイテムなど、純粋に表示だけを担当する小さなコンポーネントは、Presentational Componentとして作ります。重要なのは、各コンポーネントの責任を明確にして、修正しやすく、テストしやすい構造を保つことです。経験を積むにつれて、適切な分割のバランス感覚が身についていきます。