3.3 コンポーネント = 引数をとる関数
ここでお伝えしたいのはシンプルで、「React コンポーネントとは、props を受け取って React 要素を返すただの JavaScript 関数である」という一点です。新しい言語概念ではなく、皆さんがすでに知っている「関数」そのものなんですね。
定義
React の公式はコンポーネントを次のように定義しています。
a React component is a JavaScript function that you can sprinkle with markup.
もう少し実務的に言えば「引数 (props) を受け取り、JSX (React 要素のツリー) を返す関数」です。
function Hello() {
return <h1>こんにちは</h1>
}このように 引数をとらない コンポーネントも定義できます。引数がある場合は後述の 3.5 Props で扱います。
命名規則 — 必ず大文字で始める
コンポーネントの関数名は 必ず大文字で始める 必要があります。ここは初心者がうっかり引っかかりやすいポイントです。
React components are regular JavaScript functions, but their names must start with a capital letter or they won't work!
出典: 同上
これは JSX の仕様と結びついています。JSX は <section> のような小文字タグを「HTML 要素」、<Profile /> のような大文字タグを「React コンポーネント」と区別します。<profile /> と書いてしまうと、React は「そんな HTML タグは知らない」と解釈してしまいます。
// NG
function profile() { return <p>NG</p> }
// <profile /> は HTML タグ扱い
// OK
function Profile() { return <p>OK</p> }
// <Profile /> はコンポーネント扱い最小のコンポーネント
ボタンを 1 個表示するだけのコンポーネントを書いてみます。
// src/Button.tsx
export default function Button() {
return <button type="button">送信</button>
}export default でファイルの外から使えるようにしています。別ファイルから import して JSX の中に置きます。
// src/App.tsx
import Button from './Button'
export default function App() {
return (
<main>
<h1>サンプル</h1>
<Button />
</main>
)
}<Button /> は JavaScript のレベルでは createElement(Button, null) に変換され、React が Button 関数を呼び出します。返ってきた JSX がさらに評価され、最終的に DOM になります。
ファイル間の import / export
公式ドキュメントはエクスポート方法を 2 種類示しています。
- A file can have no more than one default export.
- A file can have as many named exports as you like.
デフォルトエクスポート
1 ファイル 1 コンポーネントのときに使うのが一般的です。
// Button.tsx
export default function Button() { /* ... */ }
// 使う側
import Button from './Button'
// 名前は変えても動く (import MyBtn from './Button' も可)名前付きエクスポート
1 ファイルに複数ある場合や、IDE の自動補完を揃えたいときに使います。
// icons.tsx
export function IconSearch() { /* ... */ }
export function IconClose() { /* ... */ }
// 使う側
import { IconSearch, IconClose } from './icons'
// 名前は完全一致が必須import 形式と export 形式を取り違えるとエラーになります。
People often use default exports if the file exports only one component, and use named exports if it exports multiple components and values.
出典: 同上
合成 (composition) — 関数を入れ子にするだけ
React の強みは「コンポーネント同士を組み合わせて大きな画面を作る」点にあります。仕組みは単純で、JSX の中に別のコンポーネントを書くだけです。
function Avatar() {
return <img src="/me.png" alt="me" width={40} height={40} />
}
function UserName() {
return <span>yuichkun</span>
}
function UserCard() {
return (
<div className="card">
<Avatar />
<UserName />
</div>
)
}
export default function App() {
return <UserCard />
}App は UserCard を呼び、UserCard は Avatar と UserName を呼びます。呼び出しの結果として出来上がるのが「コンポーネントツリー」です。
図 3.3.1 コンポーネントツリーができあがる様子
React のメンタルモデルでは、画面はこのツリーの末端 (DOM 要素) までたどって構築されます。ツリーは関数呼び出しの結果なので、関数を書く感覚でコンポーネントを組み立てられる のが特徴です。
図 3.3.2 コンポーネントツリーと画面の対応
文字だけだと分かりにくいので、「JSX で書いたツリー」と「それが画面でどのエリアになるか」を並べて見てみましょう。
ツリーの各ノードは、画面のどこかのエリアに対応します。親子関係がそのまま「囲む・囲まれる」になるので、デザインカンプを見て「ここをひとつの関数に切り出そう」と考えやすくなります。
children — 「中身」を受け取る特別な props
コンポーネントを書いていると、「外枠だけ決めて、中身は呼び出し側に任せたい」場面が出てきます。これには children という特別な props を使います。
// Card.tsx — 外枠だけを決める
type CardProps = {
children: React.ReactNode
}
export default function Card({ children }: CardProps) {
return <div className="card">{children}</div>
}
// App.tsx — 中身を与える
import Card from './Card'
export default function App() {
return (
<Card>
<h2>タイトル</h2>
<p>本文</p>
</Card>
)
}<Card>...</Card> で囲んだ内容が、Card 関数に children として渡ってきます。React.ReactNode は「React がレンダーできる値の型」で、JSX・文字列・数値・null などを含みます。
children の詳しい使い方は 3.5 Props で扱います。
再利用の動機と限度
関数と同じく、コンポーネントは次の場面で切り出すと効果が出ます。
- 同じマークアップが 2 箇所以上で繰り返される —
Button、Cardなど - 一箇所の JSX が長くて読みにくい — 関数と同じく「名前を付けて分解する」ためのツール
- ロジックと表示を分離したい — イベントハンドラや state (次章) を局所化する単位
一方、1 回しか使わない極めて短い JSX を無理に切り出すのは読みにくさの元になります。関数の粒度と同じ感覚で決めれば十分です。
さらに深く学ぶには
- react.dev — Your First Component — 定義と命名規則。
- react.dev — Importing and Exporting Components — default / named の違いと使い分け。
- react.dev — Your UI as a Tree — コンポーネントツリーのメンタルモデル。