コンポーネント
UIを再利用可能な部品(コンポーネント)に分割する方法を学びます。Reactの最も重要な概念です。
このチャプターで学ぶこと
- コンポーネントとは何かを説明できる
- 関数コンポーネントを自分で作れる
- 別ファイルにコンポーネントを作成してインポートできる
- PascalCaseの命名規則を守れる
- 自己紹介カードをHeader、Profile、Footerに分割できる
コンポーネント
前のチャプターでは自己紹介カードを App.jsx に丸ごと書きました。小さなアプリなら問題ありませんが、アプリが大きくなるにつれてコードが膨大になってしまいます。Reactではこの問題をコンポーネントという仕組みで解決します。
コンポーネントとは
コンポーネントとは「JSXを返す関数」です。それだけです。
// これがコンポーネント
function Greeting() {
return <h1>こんにちは!</h1>
}
一見シンプルですが、このコンポーネントを HTMLタグのように使える ところが革新的です。
function App() {
return (
<div>
{/* Greeting を何度でも使える */}
<Greeting />
<Greeting />
<Greeting />
</div>
)
}
Laravelのbladeコンポーネント(@component や <x-card>)に近い感覚です。UIの部品を定義して、必要な場所で使い回せます。
なぜコンポーネントに分割するのか
大きな理由は3つあります:
- 再利用性 — 同じUIを何度も書かなくて済む
- 保守性 — 「ヘッダーを直したい」なら
Header.jsxだけ触ればいい - 可読性 —
App.jsxがすっきりして全体像が把握しやすい
たとえばECサイトを想像してください。ナビバー、商品カード、カート、フッターをすべて App.jsx に書いたら地獄ですよね。それをコンポーネントに分けると:
function App() {
return (
<>
<Navbar />
<ProductList />
<Cart />
<Footer />
</>
)
}
このように App.jsx は「全体の構成図」になり、各コンポーネントが実装の詳細を持ちます。
コンポーネントを作ってみる
まずは App.jsx の中に複数のコンポーネントを書いてみましょう。
// src/App.jsx
// ヘッダーコンポーネント
function Header() {
return (
<header style={{ background: '#3b82f6', color: 'white', padding: '1rem 2rem' }}>
<h1 style={{ margin: 0 }}>私のプロフィール</h1>
</header>
)
}
// プロフィールコンポーネント
function Profile() {
const name = '山田太郎'
const bio = 'バックエンドエンジニアからフロントエンドに挑戦中です'
const hobbies = ['プログラミング', '読書', 'コーヒー']
return (
<section style={{ padding: '2rem', maxWidth: '600px', margin: '0 auto' }}>
<h2>{name}</h2>
<p>{bio}</p>
<h3>趣味</h3>
<ul>
{hobbies.map((hobby) => (
<li key={hobby}>{hobby}</li>
))}
</ul>
</section>
)
}
// フッターコンポーネント
function Footer() {
const year = new Date().getFullYear()
return (
<footer style={{ background: '#f3f4f6', padding: '1rem', textAlign: 'center' }}>
<p style={{ margin: 0 }}>© {year} 山田太郎</p>
</footer>
)
}
// App がすっきりした構成図になる
function App() {
return (
<>
<Header />
<Profile />
<Footer />
</>
)
}
export default App
コンポーネントを1つのファイルに複数書くことも、別々のファイルに分けることもできます。最初は1ファイルにまとめて書いて、慣れてきたら分割するのがおすすめです。
PascalCase命名規則
Reactではコンポーネント名は必ずPascalCase(各単語の先頭を大文字)にします。
// 正しい(PascalCase)
function UserProfile() { ... }
function NavBar() { ... }
function ShoppingCart() { ... }
// 間違い(lowercase)
function userProfile() { ... } // コンポーネントとして認識されない!
function navbar() { ... } // HTMLタグと区別できない
なぜ重要かというと、Reactはタグ名の大文字・小文字で「HTMLタグ」と「コンポーネント」を区別しているからです。
// 小文字 → HTMLのdivタグとして解釈
<div />
// 大文字 → Reactコンポーネントとして解釈
<UserProfile />
<userProfile /> と書いてもReactはHTMLタグだと思って、コンポーネントとして実行しません。これはよくある初歩的なバグの原因なので覚えておきましょう。
コンポーネントは必ずPascalCase! function myComponent() ではなく function MyComponent() です。ファイル名もコンポーネント名に合わせて UserProfile.jsx のように付けるのが慣習です。
コンポーネントをファイルに分ける
アプリが成長してきたら、各コンポーネントを別ファイルに分けましょう。Reactの一般的な規則では、1ファイル1コンポーネントです。
まずディレクトリを作ります:
src/
├── components/ ← ここにコンポーネントを置く
│ ├── Header.jsx
│ ├── Profile.jsx
│ └── Footer.jsx
├── App.jsx
└── main.jsx
src/components/ フォルダを作って、各コンポーネントを切り出しましょう。
// src/components/Header.jsx
function Header() {
return (
<header style={{ background: '#3b82f6', color: 'white', padding: '1rem 2rem' }}>
<h1 style={{ margin: 0 }}>私のプロフィール</h1>
</header>
)
}
// このファイルからHeaderを外部に公開する
export default Header
// src/components/Profile.jsx
function Profile() {
const name = '山田太郎'
const bio = 'バックエンドエンジニアからフロントエンドに挑戦中です'
const hobbies = ['プログラミング', '読書', 'コーヒー']
return (
<section style={{ padding: '2rem', maxWidth: '600px', margin: '0 auto' }}>
<h2>{name}</h2>
<p>{bio}</p>
<h3>趣味</h3>
<ul>
{hobbies.map((hobby) => (
<li key={hobby}>{hobby}</li>
))}
</ul>
</section>
)
}
export default Profile
// src/components/Footer.jsx
function Footer() {
const year = new Date().getFullYear()
return (
<footer style={{ background: '#f3f4f6', padding: '1rem', textAlign: 'center' }}>
<p style={{ margin: 0 }}>© {year} 山田太郎</p>
</footer>
)
}
export default Footer
そして App.jsx でインポートして使います:
// src/App.jsx
import Header from './components/Header'
import Profile from './components/Profile'
import Footer from './components/Footer'
function App() {
return (
<>
<Header />
<Profile />
<Footer />
</>
)
}
export default App
インポートのパスは相対パスで書きます。./components/Header の ./ は「現在のファイルと同じディレクトリ」を意味します。拡張子 .jsx は省略できます(Viteが自動で解決します)。
コンポーネントの中にコンポーネントを書く(注意)
コンポーネントの定義を別のコンポーネントの中に書くのは避けましょう。
// 悪い例:コンポーネントの中でコンポーネントを定義している
function App() {
// レンダリングのたびにButtonが再定義される!
function Button() {
return <button>クリック</button>
}
return <Button />
}
// 良い例:コンポーネントはトップレベルに定義する
function Button() {
return <button>クリック</button>
}
function App() {
return <Button />
}
コンポーネントの中でコンポーネントを定義すると、親がレンダリングされるたびに子コンポーネントが再生成されます。これはパフォーマンスの問題だけでなく、状態管理にも悪影響を与えます。
コンポーネントツリーを意識する
Reactアプリはコンポーネントのツリー構造で成り立っています。
App
├── Header
├── Profile
│ └── HobbyList(Profileの中で使うさらに小さなコンポーネント)
└── Footer
このようにコンポーネントを入れ子にして複雑なUIを組み立てていきます。Laravelでいえば、bladeのレイアウトとセクションの入れ子に近いイメージです。
前のチャプターで作った自己紹介カードを、3つのコンポーネントに分割してみましょう。
手順:
src/components/フォルダを作成するHeader.jsxを作り、サイトタイトルを表示するヘッダーを書くProfile.jsxを作り、名前・自己紹介文・趣味リストを書くFooter.jsxを作り、コピーライト表示を書くApp.jsxで3つをインポートして組み合わせる
完成イメージ:
// src/App.jsx
import Header from './components/Header'
import Profile from './components/Profile'
import Footer from './components/Footer'
function App() {
return (
<>
<Header />
<Profile />
<Footer />
</>
)
}
export default Appチェックポイント:
- 各コンポーネントのファイル名はPascalCaseになっているか?
- 各ファイルの末尾に
export defaultはあるか? App.jsxでのインポートパスは正しいか?
完成するとこんな構造になります:
ブラウザにReact DevToolsという拡張機能をインストールすると、コンポーネントツリーを視覚的に確認できます。ChromeとFirefox向けに公開されています。開発を始めたら早めに入れておくことをおすすめします。
まとめ
コンポーネントのポイントを整理します:
- コンポーネントは「JSXを返す関数」
- 名前は必ずPascalCase(
Header、UserProfileなど) export defaultでファイル外に公開し、importで読み込む- コンポーネントの定義は必ずトップレベルに書く(関数の中に書かない)
src/components/フォルダに1ファイル1コンポーネントで整理する
コンポーネントの概念は掴めましたか?でもまだ Profile.jsx の中に「山田太郎」という名前がハードコードされています。同じ Profile コンポーネントで別の人の情報を表示したい場合はどうすればいいでしょうか?
次のチャプターでは Props を使って、コンポーネントに外から値を渡す方法を学びます。