LoginSignup
0
0

【React】`as` propsでいろんなタグになるコンポーネント

Posted at

序盤

Reactで開発していて、汎用的なコンテナコンポーネントを実装するときなど、Chakra UIの一部コンポーネントにあるas propsみたいな実装をしたいと思うことがあります。

  • 例えば、TailwindCSSflex flex-row gap-2 items-center justify-betweenみたいなクラスを指定したFlowBoxコンポーネントを作ったとして、「基本はdivでいいけど、mainheaderfooterarticlesectionでもレンダリングできるようにしたいなあ」というようなケース

毎回調べてる気がするので、備忘録がてら記事にしました。

実装

Box.tsx
import { ElementType, ComponentPropsWithoutRef } from 'react';

interface Props<T extends ElementType> {
  as?: T;
}

export const Box = <T extends ElementType = 'div'>(
  props: Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>
) => {
  const { children, as: Component = 'div', ...attributes } = props;
  return <Component {...attributes}>{children}</Component>;
};

ComponentPropsWithoutRef<T>T要素のrefを除いたpropsを示す型ですが、そこからPropsで独自に定義したpropsOmit<>で落とします。

App.tsx
import { ReactNode } from 'react';
import './App.css';
import { Box } from './Box';

// Reactコンポーネント
const MyComponent = (props: { children: ReactNode }) => {
  return (
    <div>
      <p>MyComponentで{props.children}</p>
    </div>
  );
};

function App() {
  return (
    <>
      <Box />
      <Box>{'デフォルトは<div>'}</Box>
      <Box as="h1">{'<h1>でレンダリング'}</Box>
      <Box as="button" onClick={() => alert('ボタン')}>
        {'<button>でレンダリング'}
      </Box>
      <Box as={MyComponent}>{'レンダリング'}</Box>
    </>
  );
}

export default App;

結果

こうなります。
image.png

ソース
スクリーンショット 2024-04-26 20.36.44.png

特定のタグだけにしたい

これだとあらゆるタグ|コンポーネントにできてしまうので、特定のタグだけに絞ります。

Block.tsx
import { ComponentPropsWithoutRef } from 'react';

// 許容されるタグ名をUnionで。
// Reactの関数コンポーネントを許容したい場合は、`typeof {Component}`でいけます。
type AllowedElementType =
  | 'div'
  | 'main'
  | 'header'
  | 'footer'
  | 'article'
  | 'section';

interface Props<T extends AllowedElementType> {
  as?: T;
}

export const Block = <T extends AllowedElementType = 'div'>(
  props: Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>
) => {
  const { children, as: Component = 'div', ...attributes } = props;
  return <Component {...attributes}>{children}</Component>;
};
function App() {
  return (
    <>
      <Box />
      <Box>{'デフォルトは<div>'}</Box>
      <Box as="h1">{'<h1>でレンダリング'}</Box>
      <Box as="button" onClick={() => alert('ボタン')}>
        {'<button>でレンダリング'}
      </Box>
      <Box as={MyComponent}>{'レンダリング'}</Box>

      <Block />
      <Block>{'デフォルトは<div>'}</Block>
      <Block as="h1">{'<h1>でレンダリング'}</Block> {/* エラー */}
      <Block as="button" onClick={() => alert('ボタン')}> {/* エラー */}
        {'<button>でレンダリング'}
      </Block>
      <Block as={MyComponent}>{'レンダリング'}</Block> {/* エラー */}
    </>
  );
}

動かしてみる

終わりに

個人で何か作るときに楽できるように、TailwindCSSベースのオレオレUIキットを整備しつつあります。サクッと動かせればいいなら、それこそChakraUIみたいなUIコンポーネント集を使うのが合理的な選択だと思いますが、個人的には特定のコンポーネント集に対する習熟度を高めるということに、あまりモチベーションを持てずにいます。

  • それだったらReactそのものを、もっと使いこなせるようになりたいと思っています
  • 例外はHeadless UIぐらいかな

自分の好みで、適切に制限された汎用性を持ったコンポーネントを作るのが目下の楽しみです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0