Upgrade to Pro — share decks privately, control downloads, hide ads and more …

「ボタンだけどリンクにしたい…」をスマートに解決するPolymorphicコンポーネントの話

 「ボタンだけどリンクにしたい…」をスマートに解決するPolymorphicコンポーネントの話

【teamLab Study Session ~frontend~ #3 】にてチームラボのフロントエンドエンジニアが登壇用に作成したスライドです。

teamLab Study Session ~frontend~ とは

チームラボのフロントエンド班がチームラボ内で定期的に実施している勉強会を、フロントエンドの技術に興味ある参加者を募集し、オンラインで配信する勉強会です。

https://teamlab.connpass.com/event/393589/

Avatar for teamLab

teamLab PRO

June 10, 2026

More Decks by teamLab

Other Decks in Technology

Transcript

  1. © teamLab Inc. 所属| 2 自己紹介 2 パッケージチーム フロントエンド班 竹内

    颯汰 Takeuchi Sota 入社|2024年新卒入社 現在3年目 趣味|スノボ
  2. © teamLab Inc. スマートじゃない実装 7 isLinkで<a>と<button>をだし分ける デメリット • タグが増えるほどpropsや条件分岐が複雑 化する

    • button属性なのにhrefを指定できてしまう • <a>タグなのにhrefが未指定でもエラーが 出ない → Propsの型安全性が崩壊 © teamLab Inc. 7 type ButtonProps = { isLink?: boolean; href?: string; // リンク時に必要 disabled?: boolean; // ボタン時に必要 children: React.ReactNode; } & ComponentProps<'button'> & ComponentProps<'a'>; export const Button = (props: ButtonProps) => { if (props.isLink) { return <a href={props.href}>{props.children}</a>; } return <button>{props.children}</button>; }; © teamLab Inc.
  3. © teamLab Inc. Polymorphic Componentとは 10 Propsのas Polymorphic Componentではよくasを propsとして使う

    HTMLタグを動的に受容 asでHTMLタグを受け取ることで、コン ポーネントを使う側が意味(セマンティ クス)を柔軟に定義できます。 export const Button = ({ as: Component = 'button', children, ...props }) => { return ( <Component {...props}> {children} </Component> ); };
  4. © teamLab Inc. Polymorphic Componentとは 今のままだと型定義されていない • buttonなのにhrefを指定できる • asにhogeを指定できる

    11 型安全ではないので壊れやすい実装に! <Button as="button" href="#">Click</Button> <Button as="hoge" href="#">Click</Button>
  5. © teamLab Inc. TypescriptでPropsの型定義をする 13 T HTMLタグ(button / a /

    div など) を表すジェネリクス ComponentPropsWithoutRef<T> HTMLタグ T が受け取れる標準 props ButtonProps as に応じて受け取れる props が自動 で切り替わる型 type ButtonProps<T extends ElementType = 'button'> = { as?: T; children: ReactNode; } & Omit<ComponentPropsWithoutRef<T>, 'as' |'children'>;
  6. © teamLab Inc. TypescriptでPropsの型定義をする 14 T HTMLタグ(button / a /

    div など) を表すジェネリクス ComponentPropsWithoutRef<T> HTMLタグ T が受け取れる標準 props ButtonProps as に応じて受け取れる props が自動 で切り替わる型 type ButtonProps<T extends ElementType = 'button'> = { as?: T; children: ReactNode; } & Omit<ComponentPropsWithoutRef<T>, 'as' |'children'>;
  7. © teamLab Inc. TypescriptでPropsの型定義をする 15 T HTMLタグ(button / a /

    div など) を表すジェネリクス ComponentPropsWithoutRef<T> HTMLタグ T が受け取れる標準 props ButtonProps as に応じて受け取れる props が自動 で切り替わる型 type ButtonProps<T extends ElementType = 'button'> = { as?: T; children: ReactNode; } & Omit<ComponentPropsWithoutRef<T>, 'as' |'children'>;
  8. © teamLab Inc. TypescriptでPropsの型定義をする 16 type ButtonProps<T extends ElementType =

    'button'> = { as?: T; children: ReactNode; } & Omit<ComponentPropsWithoutRef<T>, 'as' | 'children'>; export const Button = <T extends ElementType = 'button'>({ as, children, ...props }: ButtonProps<T>) => { const Component = as ?? 'button'; return <Component {...props}>{children}</Component>; };
  9. © teamLab Inc. TypescriptでPropsの型定義をする 17 type ButtonProps<T extends ElementType =

    'button'> = { as?: T; children: ReactNode; } & Omit<ComponentPropsWithoutRef<T>, 'as' | 'children'>; export const Button = <T extends ElementType = 'button'>({ as, children, ...props }: ButtonProps<T>) => { const Component = as ?? 'button'; return <Component {...props}>{children}</Component>; };
  10. © teamLab Inc. 使い方 Next.jsのLinkタグやReact-ariaのButtonコンポーネントなども可能! 18 <Button as="a" href="#">Click</Button> <Button

    as="button" onClick={() => alert('Clicked!')}> Click </Button> <Button as={Link} href="#">Click</Button> <Button as={Button} onClick={() => alert('Clicked!')}>Click</Button>
  11. © teamLab Inc. 使い方(エラー例) 19 これら全て型エラーになる! ❌ buttonのpropsのhrefを指定しているので型エラー <Button as="button"

    href="#">Click</Button> ❌ HTMLタグではないhogeを指定しているので型エラー <Button as="hoge" onClick={() => alert('Clicked!')}> Click </Button>