Component VariantsのDiscriminated Unions
discriminated unionsでvariant propsをモデル化し、TypeScriptに有効なpropの組み合わせを強制させます。
「Component VariantsのDiscriminated Unions」はCoddyKit上の無料React Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはReact Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 React Academyコースには全4レッスンが含まれています。
バリアントpropsの問題
異なるモードに対応するオプショナルなpropsを持つコンポーネント(リンクにもボタンにもなるボタンなど)では、無効なpropsの組み合わせが生じることがあります。TypeScriptは、判別共用体を使わなければそれを検出できません。
判別共用体とは
判別共用体は、共通のリテラル型フィールド(判別子)を持つ型の共用体です。TypeScriptは、そのフィールドの値に基づいて型を絞り込みます。
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number };
function area(shape: Shape): number {
if (shape.kind === 'circle') return Math.PI * shape.radius ** 2;
return shape.width * shape.height; // TS knows width/height exist here
}ButtonとLinkのバリアント
ポリモーフィックコンポーネントでは、asまたはvariantフィールドの判別共用体を使って、各ケースに正しいpropsが適用されるようにします。
type ButtonProps =
| { as: 'button'; onClick: () => void; disabled?: boolean; children: React.ReactNode }
| { as: 'a'; href: string; target?: string; children: React.ReactNode };
function ActionButton(props: ButtonProps) {
if (props.as === 'button') {
return <button onClick={props.onClick} disabled={props.disabled}>{props.children}</button>;
}
return <a href={props.href} target={props.target}>{props.children}</a>;
}Alertコンポーネントのバリアント
severityの種類ごとに異なる必須データを持つAlertをモデル化します。
type AlertProps =
| { type: 'success'; message: string }
| { type: 'error'; message: string; onRetry: () => void }
| { type: 'warning'; message: string; details?: string };
function Alert(props: AlertProps) {
if (props.type === 'error') {
return (
<div className="alert error">
<p>{props.message}</p>
<button onClick={props.onRetry}>Retry</button>
</div>
);
}
return <div className={`alert ${props.type}`}>{props.message}</div>;
}イベントハンドラーでの型の絞り込み
判別共用体はイベント駆動のデータにも利用できます。ステートマシンやアクションベースの状態管理に最適です。
type LoadingState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function DataView<T>({ state }: { state: LoadingState<T> }) {
if (state.status === 'loading') return <Spinner />;
if (state.status === 'error') return <p>{state.error.message}</p>;
if (state.status === 'success') return <pre>{JSON.stringify(state.data)}</pre>;
return null;
}neverによる網羅性チェック
default分岐にneverのチェックを追加すると、新しい共用体メンバーが追加されたのに処理されていない場合、TypeScriptエラーが発生します。
function assertNever(x: never): never {
throw new Error('Unhandled case: ' + x);
}
function renderIcon(type: AlertProps['type']) {
switch (type) {
case 'success': return <CheckIcon />;
case 'error': return <XIcon />;
case 'warning': return <WarnIcon />;
default: return assertNever(type); // TS error if a case is missing
}
}APIレスポンスの判別共用体
APIレスポンスの形状を判別共用体としてモデル化すると、呼び出し側で型アサーションを使わずに成功時とエラー時の経路を処理できます。
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: string; code: number };
async function fetchUser(id: string): Promise<ApiResult<User>> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) return { ok: false, error: 'Not found', code: res.status };
return { ok: true, data: await res.json() };
}絞り込みのための型ガード
判別子が単純な等価性チェックではない複雑なケースでは、カスタム型ガードを使って共用体を絞り込みます。
function isSuccess<T>(result: ApiResult<T>): result is { ok: true; data: T } {
return result.ok === true;
}
const result = await fetchUser('1');
if (isSuccess(result)) {
console.log(result.data.name); // TS knows data exists
}オプショナルpropsの氾濫を避ける
判別共用体を使わないと、特定の組み合わせでしか有効でないオプショナルなpropsがコンポーネントに増え、分かりにくく型付けも不十分になります。判別共用体を使えば、あり得ない状態を排除できます。
// Bad: optional prop soup — invalid combos allowed:
interface BadProps {
href?: string;
onClick?: () => void;
disabled?: boolean;
}
// Good: only valid combos via discriminated union:
type GoodProps =
| { as: 'a'; href: string }
| { as: 'button'; onClick: () => void; disabled?: boolean };共用体を合成する
&(交差型)を使うと、共用体のすべてのメンバーに共通propsを追加できます。
type BaseProps = { className?: string; children: React.ReactNode };
type ButtonVariant =
| (BaseProps & { variant: 'primary'; onClick: () => void })
| (BaseProps & { variant: 'link'; href: string });実行時の判別
Reactは実行時に判別子を使って適切なUIをレンダリングします。TypeScriptはコンパイル時に判別子を使って、正しいpropsの使用を強制します。両方の層で安全性が保たれます。
確認問題
判別共用体における判別子とは何ですか?
まとめ
判別共用体は、リテラル型の判別子フィールドを共有することで、互いに排他的なpropsの集合を持つコンポーネントをモデル化します。網羅性チェックにはdefault分岐でneverを使用します。オプショナルなpropsでは許されてしまう無効なpropsの組み合わせを排除できるため、コンポーネントが自己文書化され、型安全になります。
よくある質問
「Component VariantsのDiscriminated Unions」レッスンは無料ですか?
はい。「Component VariantsのDiscriminated Unions」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、React Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 React Academyコースには全4レッスンが含まれています。
「Component VariantsのDiscriminated Unions」で何を学びますか?
discriminated unionsでvariant propsをモデル化し、TypeScriptに有効なpropの組み合わせを強制させます。 ブラウザで直接実行するハンズオンコードでReact Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
React Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのReact Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「Component VariantsのDiscriminated Unions」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このReact Academyレッスンでコードを書いて実行できますか?
はい。すべてのReact Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Component VariantsのDiscriminated Unions
- ReactにおけるConditional TypesとMapped Types
- 'as' Propを使ったPolymorphic Components
- 型安全なFormsとAPI Response Contracts