Reactコンポーネントの基本:JSXと関数コンポーネントを理解する
Reactの基本単位であるコンポーネントの作り方を解説します。JSXの書き方から関数コンポーネントの定義、propsの受け渡し、childrenの使い方まで実例付きで学べます。
Reactを使うことで何が変わるのか、まずそこから話したい。
HTMLで同じようなボタンを10個作るとき、普通はこうなる。
<button class="btn btn-primary" id="btn-1">送信</button>
<button class="btn btn-primary" id="btn-2">キャンセル</button>
<button class="btn btn-primary" id="btn-3">削除</button>文字だけが違って、あとはほぼコピーだ。10個が100個になったとき、ボタンのスタイルを変えたければ100箇所を直す。
Reactのコンポーネントは「繰り返し使える部品」だ。ボタンを一度定義すれば、何度でも使える。変更も一箇所で済む。
function Button({ label }: { label: string }) {
return <button className="btn btn-primary">{label}</button>
}
// 使うとき
<Button label="送信" />
<Button label="キャンセル" />
<Button label="削除" />これがコンポーネントの根本的な価値だ。
関数コンポーネントの基本形
Reactのコンポーネントは、JSXを返す関数として書く。
function Greeting() {
return <h1>こんにちは、React!</h1>
}
export { Greeting }命名ルール: コンポーネント名は必ず大文字始まり(PascalCase)で書く。小文字で始めると、ReactはそれをHTMLタグだと解釈してしまう。
// ✅ PascalCase:Reactコンポーネントとして扱われる
function MyButton() { return <button>クリック</button> }
// ❌ 小文字始まり:HTMLの <mybutton> タグとして解釈される(存在しないタグ)
function myButton() { return <button>クリック</button> }JSXの書き方:HTMLに似ているが違う
returnの中に書かれているHTMLのような記法が JSX だ。見た目はHTMLに近いが、実際にはJavaScriptに変換されるため、いくつかルールが違う。
ルール1:複数の要素を並べて返せない
JSXは1つのルート要素しか返せない。複数の要素をまとめるときは <>...</>(フラグメント)を使う。
// ❌ 複数のルート要素はエラー
function UserInfo() {
return (
<h2>山田 太郎</h2>
<p>フロントエンドエンジニア</p>
)
}
// ✅ フラグメントでまとめる(DOMには余分な要素が追加されない)
function UserInfo() {
return (
<>
<h2>山田 太郎</h2>
<p>フロントエンドエンジニア</p>
</>
)
}ルール2:classはclassNameと書く
class はJavaScriptの予約語なので、JSXではCSSクラスを className と書く。
// ❌ class はJavaScriptと衝突する
<button class="btn btn-primary">クリック</button>
// ✅ className を使う
<button className="btn btn-primary">クリック</button>同様に for 属性は htmlFor になる(<label htmlFor="email">)。
ルール3:JavaScriptの式は {} で埋め込む
変数や計算結果をJSXに埋め込むときは {} で囲む。
function UserCard() {
const name = '山田太郎'
const age = 28
return (
<div>
<p>{name}({age}歳)</p>
<p>成人: {age >= 18 ? 'はい' : 'いいえ'}</p>
</div>
)
}{} の中に書けるのは 式(値を返すもの)だけだ。if文 や for文 は式ではないので書けない。
// ❌ if文は式ではない
return <div>{if (isAdmin) { '管理者' }}</div>
// ✅ 三項演算子(式)なら書ける
return <div>{isAdmin ? '管理者' : '一般ユーザー'}</div>
// ✅ または && で「trueなら表示する」
return <div>{isAdmin && <AdminBadge />}</div>ルール4:自己閉じタグは / で閉じる
HTMLでは <br> や <img> は閉じタグ不要だが、JSXでは必ず閉じる。
// ❌ JSXでは閉じないとエラー
<br>
<img src="/logo.png" alt="ロゴ">
// ✅ 自己閉じタグを使う
<br />
<img src="/logo.png" alt="ロゴ" />propsでデータを渡す
同じ構造で違う内容を表示したいとき、props を使ってデータを外から渡せる。
type GreetingProps = {
name: string
role?: string // ? で省略可能にする
}
function Greeting({ name, role = 'ユーザー' }: GreetingProps) {
return (
<div>
<h2>こんにちは、{name}さん!</h2>
<p>ロール: {role}</p>
</div>
)
}使うときは属性のように書く。
function App() {
return (
<>
<Greeting name="田中" role="管理者" />
<Greeting name="鈴木" /> {/* role は省略できる */}
</>
)
}TypeScriptで型を定義すると、型に合わない値を渡したときにコンパイルエラーで気づける。
<Greeting name={123} /> // ❌ number は string に代入できない
<Greeting /> // ❌ 必須の name が指定されていない「実行して画面が壊れる前に気づける」というのがTypeScriptの価値だ。
childrenでコンテンツを内側に渡す
HTMLの <div>テキスト</div> のように、コンポーネントのタグの間に要素を書きたいとき、children を使う。
import type { ReactNode } from 'react'
type CardProps = {
title: string
children: ReactNode // タグの間に書いたものが渡ってくる
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h3 className="card-title">{title}</h3>
<div className="card-body">{children}</div>
</div>
)
}function App() {
return (
<Card title="お知らせ">
<p>新機能がリリースされました。</p>
<a href="/news">詳細を見る</a>
</Card>
)
}ReactNode はテキスト・JSX要素・配列・nullなど何でも受け取れる型だ。レイアウトやラッパーコンポーネントでよく使うパターンになる。
コンポーネントを組み合わせる
小さなコンポーネントを組み合わせて、複雑なUIを作るのがReactの使い方だ。
// 小さな部品
function Tag({ label }: { label: string }) {
return <span className="tag">{label}</span>
}
// 部品を使う中くらいの部品
function ArticleCard({ title, excerpt, publishedAt, tags }: ArticleCardProps) {
return (
<article className="card">
<h3>{title}</h3>
<p>{excerpt}</p>
<div>
{tags.map(tag => <Tag key={tag} label={tag} />)}
</div>
<time dateTime={publishedAt}>{publishedAt}</time>
</article>
)
}
// さらに組み合わせて全体を作る
function ArticleList() {
return (
<section>
{articles.map(article => (
<ArticleCard key={article.title} {...article} />
))}
</section>
)
}ツリー構造になっていくイメージだ。
App
├── Header
│ └── NavLink × 3
├── ArticleList
│ └── ArticleCard × n
│ └── Tag × n
└── Footer各コンポーネントが一つの役割を持つので、修正やテストが一箇所で済むようになる。
コンポーネントの定義とexport
コンポーネントは1ファイルに1つを基本として、named export で書くのが一般的だ。
// article-card.tsx
export function ArticleCard({ title }: { title: string }) {
return <h3>{title}</h3>
}
// 使う側
import { ArticleCard } from './article-card'default exportはimport時に好きな名前が付けられてしまい、チームでコードを読むときに混乱しやすい。named exportなら名前が統一される。
よくある初心者のミス3つ
コンポーネント内でコンポーネントを定義する
// ❌ 毎レンダリングで新しいコンポーネントが作られてしまう
function Parent() {
function Child() { // ← これはNG。Parentが再描画されるたびに新しいChildが作られる
return <p>子コンポーネント</p>
}
return <Child />
}
// ✅ トップレベルに定義する
function Child() {
return <p>子コンポーネント</p>
}
function Parent() {
return <Child />
}リストでkeyを忘れる
// ❌ key がないと警告が出る
{items.map(item => <li>{item}</li>)}
// ✅ 一意のkeyを付ける
{items.map(item => <li key={item.id}>{item.name}</li>)}propsを直接変更する
// ❌ propsは読み取り専用。変更してもReactに伝わらない
function UserCard({ user }: { user: User }) {
user.name = '変更' // NG。Reactは気づかない
return <div>{user.name}</div>
}
// ✅ stateに入れてから変更する
function UserCard({ user }: { user: User }) {
const [name, setName] = useState(user.name)
return (
<input value={name} onChange={e => setName(e.target.value)} />
)
}コンポーネントの考え方
Reactコンポーネントは「入力(props)を受け取って、表示(JSX)を返す関数」だ。同じpropsが渡れば同じ表示が返る、という単純なルールで動いている。
最初は「なぜHTMLを関数で書くんだ」と感じるかもしれない。でも部品化と再利用という考え方に慣れると、状態管理やデータフローの理解も自然とついてくる。次のステップとして useState を学ぶと、コンポーネントにインタラクティブな動作を加えられるようになる。