コンポーネント
この章では、Reactの基本単位であるコンポーネントについて学びます。
この章で学ぶこと
- コンポーネントとは何か、なぜ使うのか
- 関数コンポーネントの書き方
- コンポーネントの分割と再利用
- ファイル分割のベストプラクティス
この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。
コンポーネントとは
コンポーネントは、UIを独立した再利用可能なパーツに分割したものです。ボタン、フォーム、カードなど、あらゆるUI要素をコンポーネントとして定義できます。
なぜコンポーネントに分割するのでしょうか?主な理由は以下の3つです:
- 再利用性: 同じUIパーツを複数箇所で使い回せる
- 保守性: 変更が必要な箇所が局所化される
- 可読性: 大きなUIを小さな単位で理解できる
関数コンポーネント
現在のReactでは、関数コンポーネントが標準です。
function Greeting() {
return <h1>こんにちは!</h1>
}
アロー関数でも記述できます。※アロー関数については02章で解説しています。
const Greeting = () => {
return <h1>こんにちは!</h1>
}
React.FC は使わない
型定義の話を先取りすると、関数コンポーネントに React.FC<Props> という型を付ける書き方を見かけることがありますが、本書では使いません。引数の型を直接指定するだけで十分で、React.FC にはいくつか不便な点があります。
// 推奨: 引数の型を直接指定
function Greeting({ name }: { name: string }) {
return <h1>こんにちは、{name}さん!</h1>
}
// 非推奨: React.FC
const Greeting: React.FC<{ name: string }> = ({ name }) => {
return <h1>こんにちは、{name}さん!</h1>
}
React 公式ドキュメントや TypeScript コミュニティでも、近年は React.FC を使わないスタイルが主流です。
関数コンポーネント vs クラスコンポーネント
React 16.8以前は「クラスコンポーネント」が主流でしたが、Hooks(07章〜)の登場により関数コンポーネントが標準になりました。
| 種類 | 特徴 |
|---|---|
| 関数コンポーネント | シンプル、Hooks使用可、現在の標準 |
| クラスコンポーネント | 複雑、レガシーコードで見かける |
クラスコンポーネントは24章で解説しますが、新規開発では関数コンポーネントを使用してください。
コンポーネントの命名規則
コンポーネント名はパスカルケース(先頭大文字)で命名します。
// OK: パスカルケース
function UserProfile() { /* ... */ }
function ShoppingCart() { /* ... */ }
// NG: 小文字始まり(HTMLタグと区別できない)
function userProfile() { /* ... */ }
小文字で始まるコンポーネント名はHTMLタグとして解釈されます。<button>はHTML要素ですが、<Button>はReactコンポーネントです。この違いは非常に重要です。
コンポーネントの使用
コンポーネントはHTMLタグのように使用します。
function App() {
return (
<div>
<Greeting />
<Greeting />
</div>
)
}
試してみよう: コンポーネントを作って使う
src/App.tsxを以下のように書き換えて、Greetingコンポーネントを定義し、複数回使用してみましょう。
ヒント
function Greeting()でコンポーネントを定義- JSXで
<Greeting />を複数回配置
回答と解説
// src/App.tsx
import './App.css'
function Greeting() {
return <p>こんにちは!</p>
}
function App() {
return (
<div>
<h1>挨拶アプリ</h1>
<Greeting />
<Greeting />
<Greeting />
</div>
)
}
export default App
コンポーネントは何度でも再利用できます。<Greeting />を3回配置すると、「こんにちは!」が3回表示されます。これがコンポーネントの再利用性です。
コンポーネントの分割
大きなコンポーネントは、小さなコンポーネントに分割することで管理しやすくなります。
分割前
function App() {
return (
<div>
<header>
<nav>
<a href="/">ホーム</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<h1>ようこそ</h1>
<p>サイトの説明文...</p>
</main>
<footer>
<p>© {new Date().getFullYear()} My Site</p>
</footer>
</div>
)
}
export default App
分割後
function Header() {
return (
<header>
<nav>
<a href="/">ホーム</a>
<a href="/about">About</a>
</nav>
</header>
)
}
function Main() {
return (
<main>
<h1>ようこそ</h1>
<p>サイトの説明文...</p>
</main>
)
}
function Footer() {
return (
<footer>
<p>© {new Date().getFullYear()} My Site</p>
</footer>
)
}
function App() {
return (
<div>
<Header />
<Main />
<Footer />
</div>
)
}
export default App
ファイル分割
コンポーネントは別ファイルに分割し、import/exportで使用します。
src/
├── components/
│ ├── Header.tsx
│ ├── Main.tsx
│ └── Footer.tsx
└── App.tsx
// src/components/Header.tsx
export function Header() {
return (
<header>
<nav>
<a href="/">ホーム</a>
<a href="/about">About</a>
</nav>
</header>
)
}
// src/App.tsx
import { Header } from './components/Header'
import { Main } from './components/Main'
import { Footer } from './components/Footer'
function App() {
return (
<div>
<Header />
<Main />
<Footer />
</div>
)
}
export default App
試してみよう: ファイル分割を実践する
以下の手順でコンポーネントをファイル分割してみましょう。
src/components/Header.tsxを作成し、サイトのタイトルとナビゲーションを表示src/components/Footer.tsxを作成し、コピーライトを表示src/App.tsxでインポートして使用
ヒント
export function Header()で関数を定義し、<header>タグ内にタイトルとナビゲーションを配置export function Footer()で関数を定義し、<footer>タグ内にコピーライトを配置import { Header } from './components/Header'でインポートし、<Header />として使用
回答と解説
Header.tsx
// src/components/Header.tsx
export function Header() {
return (
<header>
<h1>My App</h1>
<nav>
<a href="/">ホーム</a>
<a href="/about">About</a>
</nav>
</header>
)
}
exportキーワードを付けることで、他のファイルからインポートできます。
Footer.tsx
// src/components/Footer.tsx
export function Footer() {
return (
<footer>
<p>© {new Date().getFullYear()} My App. All rights reserved.</p>
</footer>
)
}
©はHTMLエンティティで、©記号を表示します。
App.tsx
// src/App.tsx
import './App.css'
import { Header } from './components/Header'
import { Footer } from './components/Footer'
function App() {
return (
<div>
<Header />
<main>
<h2>ようこそ</h2>
<p>メインコンテンツがここに入ります</p>
</main>
<Footer />
</div>
)
}
export default App
インポートパスは相対パス(./から始まる)で指定します。コンポーネントは自己終了タグ<Header />で使用するのが一般的です。
コンポーネント設計のポイント
1. 単一責任
各コンポーネントは1つの責任を持つようにします。
2. 再利用性
汎用的に使えるコンポーネントを意識して設計します。
3. 適切な粒度
細かすぎず、大きすぎない適切な粒度で分割します。
コンポーネントの分割に正解はありません。プロジェクトの規模やチームの方針に合わせて調整しましょう。
いつコンポーネントを分割すべきか?
以下のような場合に分割を検討しましょう:
- 同じUIパターンが2回以上出現する → 再利用のため分割
- 1つのコンポーネントが100行を超える → 可読性のため分割
- 複数の責任を持っている → 単一責任のため分割
- テストが書きにくい → テスタビリティのため分割
逆に、分割しすぎると「props drilling」(propsのバケツリレー)が発生し、かえって複雑になることもあります。※props drillingの解決策は13章(useContext)で解説します。
まとめ
- コンポーネントはUIを再利用可能な単位に分割したもの
- 関数コンポーネントが現在の標準
- 名前はパスカルケースで命名
- 適切な粒度でファイル分割する
次の章では、コンポーネントにデータを渡すPropsについて学びます。