第17章:部品を別のファイルに分ける
— export / import を使って、キレイに整理しよっ!—
きょうのゴール 🎯
- コンポーネントを別ファイルに分ける手順が分かる
- default export と named export の違いが分かる
- ありがちエラーを秒速で解決できる
まずは全体イメージをつかもう 🧭
ポイント:
- App.tsx が components フォルダの部品をimportして使う流れだよ🌸
ステップ1:フォルダを作ってファイル分割 ✂️
src/の中にcomponents/フォルダを作るHelloNameを別ファイルに移動してみよう
// src/components/HelloName.tsx
type Props = {
name: string;
emoji?: string;
};
export default function HelloName({ name, emoji = "🌷" }: Props) {
return <p>こんにちは、{name} さん {emoji}</p>;
}
App.tsx から呼び出す👇
// src/App.tsx
import HelloName from "./components/HelloName";
export default function App() {
return (
<main style={{ padding: 16 }}>
<h1>ファイル分割の第一歩✨</h1>
<HelloName name="Hanako" emoji="💖" />
<HelloName name="Taro" />
</main>
);
}
うまくいけばブラウザに2つの挨拶が出るはず!🙌 Vite + React v19 なら
import React from "react"は不要でOKだよ。
ステップ2:default export と named export の使い分け 🧠
どっちを使う?
- default export:ファイルに「主役」が1つのとき。
import X from "…" - named export:複数のユーティリティや小部品をまとめたいとき。
import { X, Y } from "…"
default の例(さっきのやつ)
// src/components/ProfileCard.tsx
type Props = { name: string; tag: string; icon: string; bio?: string };
export default function ProfileCard({ name, tag, icon, bio }: Props) {
return (
<article style={{ border: "1px solid #eee", padding: 12, borderRadius: 12 }}>
<img src={icon} alt={`${name} のアイコン`} width={56} height={56} style={{ borderRadius: "50%" }} />
<div>
<strong>{name}</strong> <span style={{ color: "#666" }}>@{tag}</span>
{bio && <p style={{ marginTop: 6 }}>{bio}</p>}
</div>
</article>
);
}
使い方👇
import ProfileCard from "./components/ProfileCard";
named の例(小部品をセットで)
// src/components/Text.tsx
export function Title({ children }: { children: React.ReactNode }) {
return <h2 style={{ marginBlock: 8 }}>{children}</h2>;
}
export function Muted({ children }: { children: React.ReactNode }) {
return <p style={{ color: "#666" }}>{children}</p>;
}
使い方👇
import { Title, Muted } from "./components/Text";
default と named を同じファイルから同時に取ることもあるよ
// src/components/Button.tsx
export default function Button({ children, ...rest }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
return <button {...rest} style={{ padding: "8px 12px", borderRadius: 8 }}>{children}</button>;
}
export function Danger({ children, ...rest }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
return <button {...rest} style={{ padding: "8px 12px", borderRadius: 8, background: "#ff4d4f", color: "#fff" }}>{children}</button>;
}
使い方👇
import Button, { Danger } from "./components/Button";
おまけ:名前を変えて import したいときは
asが使えるよ👇import { Danger as DangerButton } from "./components/Button";
ステップ3:パスの書き方のミニルール 🛣️
- 相対パスでOK:
"./components/HelloName" - 拡張子は省略でOK:
.tsxは書かない - スラッシュは前向き:Windows でも
/を使う(.\じゃないよ) ..が増えたら(../../地獄)、第149章の絶対パスインポートで解決するよ✨
ありがちエラーと秒速リカバリ 🧯
-
Cannot find module → import のパスを確認、大文字小文字も要チェック
-
Attempted import error(default なのに
{}で取ってる 等) → 書き方を合わせる:- default は
import X from "…", - named は
import { X } from "…"
- default は
-
JSX adjacent elements → 兄弟要素を
<> ... </>で包む
ハンズオン:部品を分割して組み立てる 🧪🧩
-
src/components/に 3つ作るAvatar.tsx(default)UserName.tsx(default)UserTag.tsx(named)
-
ProfileCard.tsx(default)で組み立て -
App.tsxに表示
サンプル実装👇
// src/components/Avatar.tsx
type Props = { src: string; size?: number; alt?: string };
export default function Avatar({ src, size = 56, alt = "avatar" }: Props) {
return <img src={src} alt={alt} width={size} height={size} style={{ borderRadius: "50%", objectFit: "cover" }} />;
}
// src/components/UserName.tsx
export default function UserName({ children }: { children: React.ReactNode }) {
return <strong style={{ fontSize: 18 }}>{children}</strong>;
}
// src/components/UserTag.tsx
export function UserTag({ children }: { children: React.ReactNode }) {
return <span style={{ color: "#666" }}>@{children}</span>;
}
// src/components/ProfileCard.tsx
import Avatar from "./Avatar";
import UserName from "./UserName";
import { UserTag } from "./UserTag";
type Props = { name: string; tag: string; icon: string; bio?: string };
export default function ProfileCard({ name, tag, icon, bio }: Props) {
return (
<article style={{ display: "flex", gap: 12, alignItems: "center", padding: 12, borderRadius: 12, border: "1px solid #eee" }}>
<Avatar src={icon} />
<div>
<UserName>{name}</UserName> <UserTag>{tag}</UserTag>
{bio && <p style={{ marginTop: 6 }}>{bio}</p>}
</div>
</article>
);
}
// src/App.tsx
import ProfileCard from "./components/ProfileCard";
export default function App() {
return (
<main style={{ padding: 16 }}>
<h1>People ✨</h1>
<ProfileCard name="Hanako" tag="hana_dev" icon="https://placehold.co/96x96" bio="抹茶ラテとTSが好き" />
<ProfileCard name="Mina" tag="mina_ui" icon="https://placehold.co/96x96" />
</main>
);
}
さらに一歩:軽い再エクスポート(予告編)📤
※ 本格版は 第150章 でやるよ!
// src/components/index.ts
export { default as Avatar } from "./Avatar";
export { default as UserName } from "./UserName";
export { UserTag } from "./UserTag";
export { default as ProfileCard } from "./ProfileCard";
使い方👇
// src/App.tsx
import { ProfileCard } from "./components";
export default function App() {
return <ProfileCard name="Riko" tag="riko_ui" icon="https://placehold.co/96x96" />;
}
チェックリスト ✅
src/components/に分割できた?- default / named を正しく import した?
- 兄弟JSXは
<>...</>で包んだ? - パスは "./components/..." でOK?(拡張子いらないよ)
ミニテスト(3問)📝
- default export を import する正しい書き方は?
- named export を import する正しい書き方は?
Attempted import errorが出たらまずどこを疑う?
こたえ
import X from "./path/X"import { X } from "./path/X"- default と named の取り違え と import パス
まとめ 💡
- コンポーネントは1ファイル1主役でスッキリ
- default は主役、named はサブ達
- エラーはパスとdefault/namedの確認でほぼ解決✨
次は 第18章!
type と interface の選び方をサクッと掴んで、型安全をさらに強化しようね〜🛡️💙