AI に書いてもらった variant コンポーネントに見た目(variant)を1つ足したら、型が合わずビルドが落ちた。原因を後から追って腑に落ちた「デフォルト値の型ずれ」を、進行形の学習ログとして残す。コードは要点だけ抜き出した形にしている。
この記事でわかること
- variant ごとに型が違う prop にデフォルト引数を置くと何が起きるか
- なぜそれが危険なのか(ビルド壊滅パターン)
- 「デフォルトを置かず必須にする」で構造的に防ぐ考え方
何が起きたか
綴Photo の LP を別パターンの見せ方でも出せるようにしたくて、classic 用に AI に書いてもらったコンポーネントに、後から variant="pop" を足した。<MultiVariantSection variant="pop" /> と呼んで表示してみると──セクションが丸ごと空白。ビルド(next build)を通すと TypeScript のコンパイルエラーで止まります。
原因は単純で、pop 専用のプロパティに、classic 用のデフォルト値が刺さっていた。
前提:呼び出し方が classic と pop で違う
このコンポーネントは pairs プロパティでデータを受け取る作りで、classic も pop も pairs を読んで表示します。違うのは呼び出し側で pairs を渡しているかどうか:
// 元々の使い方(classic だけ)
<MultiVariantSection variant="classic" pairs={CLASSIC_PAIRS} />ところが pop を後から足した時、呼び出し側はこう書いた:
<MultiVariantSection variant="pop" /> // ← pairs を省略やりがちな書き方(これがビルドを落とす)
上の状況をコードで見るとこうなります(要点だけ抜き出している)。
type PopPair = {
leftTag: string;
leftText: string;
rightTag: string;
rightText: string;
};
type Props = {
variant?: 'classic' | 'pop';
pairs?: PopPair[]; // ← 型は PopPair[]
};
// CLASSIC_PAIRS は { voice, answer }[] 型(PopPair と全く別の型)
const CLASSIC_PAIRS = [
{ voice: '...', answer: '...' },
// ...
];
export default function MultiVariantSection({
variant = 'classic',
pairs = CLASSIC_PAIRS, // ← ここ! PopPair[] に { voice, answer }[] を渡している
}: Props) {
if (variant === 'pop') {
return (
<>
{pairs.map((pair) => (
<Bubble tag={pair.leftTag}>
{pair.leftText}
</Bubble>
))}
</>
);
}
// ...
}なぜ危険か
next build は公開できる形に組み立てる工程であり、ここで止まるとデプロイ=公開ができません。すり抜けた経路はエラーが console に出ないケースがあり、「なんか表示されてないな」程度で気づかれにくいです。
避け方:デフォルト引数を取らず、呼び出し側で必ず渡す
「デフォルトを POP_PAIRS に変える」でも目の前のバグは消えます。けれど variant ごとに型が違う prop にデフォルトを置くこと自体が地雷で、新しい variant を足すたびに同じ事故が起きます。
筋がいいのはデフォルト引数を取らず、呼び出し側で必ず渡す形にすること。
// 関数側:デフォルト引数を消す
export default function MultiVariantSection({
variant = 'classic',
pairs, // ← デフォルト値を置かない
}: Props) {// 呼び出し側:variant に対応する pairs を毎回明示
<MultiVariantSection variant="classic" pairs={CLASSIC_PAIRS} />
<MultiVariantSection variant="pop" pairs={POP_PAIRS} />新しい variant(minimal, dark ...)が増えても、その都度「専用のデータを明示的に渡す」運用が続くので、デフォルト経由でデータが混線する事故が構造的に起きません。
学びポイント
- デフォルト引数は呼び出し側が省略したときだけ効きます。普段は明示的に
pairsを渡していてバグが顔を出さず、variant を足してデフォルトに頼った瞬間に初めて踏む ── だから「うっかり」ではなく、この設計なら誰でもハマる方向の落とし穴 - variant ごとに型が違う prop には、そもそもデフォルト引数を置きません。「片方の variant 用」のデフォルトは、別 variant が増えるたびに同じ事故の種になります
- 代わりに型側でオプショナルを外して必須にし、呼び出し側で必ず渡させます。TypeScript が「省略禁止」を強制してくれるので、同じ間違いを構造的に防げます
- classic と pop でデータ型が違うなら、
pairsプロパティの型を union(=「この型 or あの型」とどちらでもOKと宣言する)にするか variant ごとに分離するか、設計段階で決めておくと後のミスが減ります
おわりに
直すのは数文字でも、踏むと影響は「ビルド失敗 + セクション丸ごと空白」という破壊力。コード量が少ない変更ほど、デフォルト値やプロパティ名の見落としが起きやすいです。
綴Photo は実装を AI に書いてもらう進め方をしていて、variant で受け取る型が違うこのセクションもそうやって組んだ。最初は「デフォルトを片方用に置いたまま、もう片方を呼んだ」ことに気づかず、ビルドが落ちてから後追いで腑に落ちた。直す段で「そもそもデフォルトを置かない」に倒せば、新しい variant を足すたびに同じ事故を踏むルートが構造的に塞がる ── 目の前のバグを消すだけでなく、設計を一段固くする方向で直す視点を持ちたい。
この記事は2026年4月時点の自分の理解で書いています。TypeScript の型推論や React の variant パターン自体は安定した領域だが、
shadcn/uiや CVA など variant を扱う周辺ツールはアップデートが入ることがあるので、最新の公式ドキュメントも合わせて参照してください。