0Pricing
React Academy · レッスン

'as' Propを使ったPolymorphic Components

'as' propに型を付け、正しいprop typesを保ったままButtonを または としてrenderします。

「'as' Propを使ったPolymorphic Components」はCoddyKit上の無料React Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはReact Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 React Academyコースには全4レッスンが含まれています。

ポリモーフィックコンポーネントとは

ポリモーフィックコンポーネントは、as propに基づいて異なるHTML要素やコンポーネントとしてレンダリングされます。たとえば、正しいTypeScriptの型を保ったまま、Buttonを<a>または<button>としてレンダリングできます。

シンプルなas prop(TypeScriptなし)

ジェネリクスを使わない基本的なポリモーフィック実装です。型安全性は失われますが、概念を示すことができます。

function Box({ as: Tag = 'div', children, ...props }) {
  return <Tag {...props}>{children}</Tag>;
}

// Usage:
<Box as="section" className="hero">...</Box>
<Box as="article">...</Box>

ジェネリクスによる型付きas prop

ジェネリック型パラメーターを追加して、asがどの要素をレンダリングするかに応じた正しいpropsを推論できるようにします。

type PolymorphicProps<E extends React.ElementType> = {
  as?: E;
} & Omit<React.ComponentPropsWithoutRef<E>, 'as'>;

function Box<E extends React.ElementType = 'div'>({
  as,
  ...props
}: PolymorphicProps<E>) {
  const Tag = as ?? 'div';
  return <Tag {...props} />;
}

ref対応

ポリモーフィックな型付けを維持したまま、forwardRefに対応させます。

type PolymorphicRef<E extends React.ElementType> = React.ComponentPropsWithRef<E>['ref'];

type PolymorphicPropsWithRef<E extends React.ElementType, P = {}> = P &
  Omit<React.ComponentPropsWithRef<E>, keyof P> & { as?: E };

const Button = React.forwardRef(
  <E extends React.ElementType = 'button'>(
    { as, ...props }: PolymorphicPropsWithRef<E>,
    ref: PolymorphicRef<E>
  ) => {
    const Tag = as ?? 'button';
    return <Tag ref={ref} {...props} />;
  }
);

TypeScriptの型推論を実際に確認する

as='a'を設定すると、TypeScriptはコンポーネントがhrefやtargetなどアンカー固有のpropsを受け付ける一方、disabledのようなボタン固有のpropsは受け付けないことを認識します。

// TypeScript allows href because as='a':
<Box as="a" href="https://example.com">Link</Box>

// TypeScript errors if you pass href to a div:
<Box as="div" href="...">Error!</Box> // TS: href is not a valid div prop

カスタムpropsで拡張する

ポリモーフィックな基本型にコンポーネント固有のpropsを加えるには、それらを型定義に展開します。

type TextProps<E extends React.ElementType> = PolymorphicProps<E> & {
  size?: 'sm' | 'md' | 'lg';
  weight?: 'normal' | 'bold';
};

function Text<E extends React.ElementType = 'p'>({
  as,
  size = 'md',
  weight = 'normal',
  className,
  ...props
}: TextProps<E>) {
  const Tag = as ?? 'p';
  return (
    <Tag
      className={[`text-${size}`, `font-${weight}`, className].filter(Boolean).join(' ')}
      {...props}
    />
  );
}

実際の使用例:RouterLinkとしてレンダリングするLink

toが指定されている場合はReact RouterのLinkとして、指定されていない場合は通常のボタンとしてレンダリングされるButtonです。

import { Link } from 'react-router-dom';

type ButtonProps =
  | { to: string; href?: never } & React.ComponentPropsWithoutRef<typeof Link>
  | { href: string; to?: never } & React.ComponentPropsWithoutRef<'a'>
  | { to?: never; href?: never } & React.ComponentPropsWithoutRef<'button'>;

function Button({ to, href, ...props }: ButtonProps) {
  if (to) return <Link to={to} {...props} />;
  if (href) return <a href={href} {...props} />;
  return <button type="button" {...props} />;
}

デザインシステムのTextコンポーネント

ポリモーフィックなTextコンポーネントです。デフォルトでは<p>としてレンダリングされますが、h1〜h6、span、labelとしてもレンダリングできます。

<Text as="h1" size="xl">Page Title</Text>
<Text as="span" weight="bold">Inline bold</Text>
<Text as="label" htmlFor="email">Email</Text> // htmlFor is valid because as='label'

as propに対する制約

extends React.ElementTypeを使うと、HTML要素の文字列('div'や'a')とReactコンポーネントの両方を受け付けられます。

function Card<E extends React.ElementType = 'div'>({ as, ...props }: PolymorphicProps<E>) {
  const Tag = as ?? 'div';
  return <Tag {...props} />;
}

// Works with HTML elements:
<Card as="section" aria-label="Products" />
// Works with React components:
<Card as={motion.div} animate={{ opacity: 1 }} />

よくある落とし穴を避ける

as propを要素に展開しないでください(有効なHTML属性ではありません)。rest propsを展開する前に、必ず分割代入で取り出してください。

// Bad:
function Box({ as: Tag = 'div', ...props }) {
  return <Tag as={...} {...props} />; // 'as' attribute on div is invalid HTML
}

// Good:
function Box({ as: Tag = 'div', ...props }) {
  return <Tag {...props} />; // 'as' is used to pick Tag, not passed to element
}

パフォーマンスに関する考慮事項

ポリモーフィックコンポーネントによるパフォーマンスのオーバーヘッドは最小限です。as propは、要素型の文字列またはコンポーネント参照を保持する単なる変数だからです。

確認問題

ポリモーフィックコンポーネントでrest propsを要素に展開する前に、as propを分割代入する必要があるのはなぜですか?

まとめ

ポリモーフィックコンポーネントは、任意の要素としてレンダリングしながら正しいTypeScriptのpropsを維持するために、ジェネリックなas?: E extends React.ElementType propを使用します。rest propsを展開する前にasを分割代入してください。型に要素固有のすべての属性を含めるには、React.ComponentPropsWithoutRefを使用します。

よくある質問

「'as' Propを使ったPolymorphic Components」レッスンは無料ですか?

はい。「'as' Propを使ったPolymorphic Components」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、React Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 React Academyコースには全4レッスンが含まれています。

「'as' Propを使ったPolymorphic Components」で何を学びますか?

'as' propに型を付け、正しいprop typesを保ったままButtonを または としてrenderします。 ブラウザで直接実行するハンズオンコードでReact Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

React Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのReact Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「'as' Propを使ったPolymorphic Components」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このReact Academyレッスンでコードを書いて実行できますか?

はい。すべてのReact Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Component VariantsのDiscriminated Unions
  2. ReactにおけるConditional TypesとMapped Types
  3. 'as' Propを使ったPolymorphic Components
  4. 型安全なFormsとAPI Response Contracts
← React Academyに戻る