こんにちは!Reactの学習、毎日お疲れ様です。
「よし、コンポーネントを綺麗にパーツ分けできるようになってきたぞ!」と楽しくなってきた頃に、ふとこんな壁にぶつかったことはありませんか?
「ボタンコンポーネントを作ったはいいけれど、ある時は `
……分かります、その気持ち。めちゃくちゃ痛いほど分かります。私も昔、画面の前で頭を抱え込んでコーヒーを何杯も飲んだものです。
今回は、そんなReact中級者へのステップアップを阻む大きな山「Polymorphic Components(ポリモーフィック・コンポーネント)」の型定義について、身近な例えを交えながら、とことん優しく解きほぐしていきますね。
大丈夫、一歩ずつ紐解けば決して怖くありません。それでは、一緒に見ていきましょう!
—
そもそも「ポリモーフィック」ってなに?
カタカナで書かれると一気に難しく感じますが、英語の「Poly(多様な)+ Morph(形態・姿)」が合体した言葉です。つまり、「状況に応じて自分の姿(HTMLタグ)をカメレオンのように変えられるコンポーネント」のこと。
身近な例えで考えてみましょう。
あなたの手元にある「万能なリモコン」をイメージしてください。
テレビに向ければチャンネルを変えるボタンになり、エアコンに向けければ温度を変えるボタンになり、照明に向けければ明るさを変えるボタンになる。リモコン本体(コンポーネント)は一つなのに、指す相手によって中身の役割がガラリと変わりますよね。
Web制作でも、「見た目は全く同じボタンデザインなのに、ある時はフォーム送信用の `
これを実現するために用意するのが、お馴染みの `as` 属性 です。
{/ ボタンタグとして使いたいとき /}
({/ リンクとして使いたいとき /}
「なんだ、 `as` 属性をつければいいだけじゃん!」と思いますよね。
はい、JavaScript(またはプレーンなReact)の動的なレンダリングだけなら、実はそれほど難しくありません。問題は、「TypeScriptの型チェックを完璧に効かせること」なのです。
—
つまずきポイント:`as=”a”` なのに `href` が補完されない絶望
ここに、次のような「普通のボタンコンポーネント」の型定義があるとします。
type ButtonProps = {
as?: ‘button’ | ‘a’;
children: React.ReactNode;
};
これだと、何が困るでしょうか?
もしユーザーが `
しかし、さっきの単純な型定義のままだと、TypeScriptは「`as` が何であれ、同じ型ルールを適用する」ため、`href` を書き忘れてもエラーを出してくれないし、逆に `button` なのに `href` を書いても怒ってくれません。エディタの親切なコード補完も「うーん、どっちのタグのつもりか分からないよ〜」とフリーズしてしまいます。
これを解決するのが、今回のお題である「高度なポリモーフィックの型定義」です。
—
魔法のレシピ:ジェネリクスとポリモーフィックの型設計
「うっ、ジェネリクス(Generics)とか出てきたよ……難しそう……」と思った方、ちょっと待ってください!深呼吸してくださいね。
ここでは難しい理論は置いといて、実務でそのままコピーして使える「お守りのような型定義のテンプレート」をお見せします。まずはこの形を眺めてみましょう。
import React from ‘react’;
// 1. コンポーネントが受け取る独自のプロパティを定義するよ
type ButtonOwnProps
as?: E;
children: React.ReactNode;
};
// 2. 「独自のプロパティ」と「選ばれたHTMLタグ本来の属性(onClickやhrefなど)」を合体させる型を作るよ
type ButtonProps
Omit
// 3. コンポーネント本体(ここが少し呪文っぽく見えるけど大丈夫!)
export const Button =
{ as, children, …rest }: ButtonProps
) => {
// 指定されたタグ(なければデフォルトで ‘button’)を代入するよ
const Component = as || ‘button’;
return (
{children}
);
};
お疲れ様です!なんだか見慣れない記号が並びましたが、やっていることはすごくシンプルです。お買い物の流れに例えてみましょう。
1. `ButtonOwnProps`(お店の基本ルール):
「ウチのお店では、`as`(どのタグにする?)と `children`(中身のテキスト)は絶対用意してね」という共通のルールブックです。
2. `ButtonProps`(オーダーメイドの組み合わせ):
お客さんが「今回は `a` タグにするよ!」と言ったら、`a` タグ本来が持っている属性(`href` や `target` など)を引っ張り出してきて、さっきの基本ルールとガチャコンと合体させます。
3. `Component`:
実際に画面に描き出すときに、「今回はどの姿に変身するんだっけ?」と思い出して、そのタグに変身します。
—
実際に使ってみましょう!
この `Button` コンポーネントを、実際の画面でどう使うか見てみましょう。エディタの動きを想像しながら読んでみてくださいね。
function App() {
return (
{/ パターン1: 普通のボタンとして使う /}
>
保存する
{/ パターン2: リンクとして使う /}
Reactの公式サイトへ行く
);
}
ここで感動してほしいポイントがあります!
パターン2で `as=”a”` と指定した瞬間、TypeScriptのエディタはこんな風にあなたをサポートしてくれます。
- `href` 属性を書くと、しっかり補完が効いて気持ちよく入力できる。
- 万が一、`as=”button”` なのに `href` を書こうものなら、エディタが「おいおい、ボタンに `href` はおかしいぞ!」と赤く波線を出して教えてくれる。
これぞ、私たちが型定義をがんばる最大の理由です。「うっかりミスを、公開する前にエディタが全速力で防いでくれる」という安心感。最高ですね。
—
チーフアーキテクトからの優しいアドバイス
ポリモーフィックなコンポーネントは、一度書き方を覚えてしまえば、デザインシステムやUIライブラリ(Chakra UIやMUIなど)の裏側を覗いているようなワクワク感を味わえます。
ただ、初心者の方や、チームメンバーのスキルセットによっては、少しコードが複雑に感じられて「読むのがつらい……」となってしまうことも事実です。
だからこそ、プロジェクトの初期段階で無理にすべてをポリモーフィックにする必要はありません。
「ここは絶対にあらゆるタグに変身できなきゃ困る!」という共通のボタンやテキストコンポーネントに出会ったとき、今日紹介したコードをそっとお守り代わりに思い出してください。
もしコード書いていて「うわ、型エラーが消えないよ……!」と泣きそうになったら、深呼吸して一つずつプロパティを見直していきましょう。あなたは一人じゃありません、焦らずゆっくり進めていきましょうね。
それでは、快適なReactライフを!

コメント