본문 바로가기

공부기록/Typescript

# 54 polymorphic component 만들기 : Container

 

 

 

ElementType


 

컴포넌트의 유효한 indentifier여야 함

 

as라는  속성으로 받아온 엘리먼트 식별자를 

Container로 만들어 줌

 

as는 타입이 ElementType

 

import { ElementType } from "react";

type ContainerProps = {
  as: ElementType;
};

const Container = ({ as: Component }: ContainerProps) => {
  return <Component />;
};

export default Container;

 

 

 

        <Container as={Button}>Click Me</Container>

 

 

 

Children 받아오기


 

import { type ReactNode, type ElementType } from "react";

type ContainerProps = {
  as: ElementType;
  children: ReactNode;
};

const Container = ({ as: Component, children }: ContainerProps) => {
  return <Component>{children}</Component>;
};

export default Container;

 

 

ComponentPropsWithoutRef 설정하기


 

동적으로 ElementType을 받아오므로, 

해당 타입을 하드코딩 할 수 없다. 

 

제네릭 타입을 활용 

 

 

T는 구성요소 식별자. 

 

그렇게 되면 T가 아무것이나 올 수 있으므로 

extends로 ElementType을 사용하여 

ElementType만 올 수 있게 제한해본다.

 

 

 

import {
  type ReactNode,
  type ElementType,
  ComponentPropsWithoutRef,
} from "react";

type ContainerProps<T extends ElementType> = {
  as: T;
  children: ReactNode;
} & ComponentPropsWithoutRef<T>;

const Container = ({ as: Component, children }: ContainerProps) => {
  return <Component>{children}</Component>;
};

export default Container;

 

 

그러면 아래와 같은 에러가 뜬다 

 

 

 

 

 

구체적인 타입을 정의해야 하므로.

 

 

유효한 component identifier와 함께 사용되어야 하므로 

하드코딩을 할 수는 없다. 

 

 

 

함수의 유형을 받아들이는 제네릭 함수를  Container에 지정해본다. 

 

 

ContainerProps는 제네릭 함수로 구체적으로 지정해야 하게 되었다. 그러므로 아래와 같이 지정해준다.

 

 

 

 

import {
  type ReactNode,
  type ElementType,
  ComponentPropsWithoutRef,
} from "react";

type ContainerProps<T extends ElementType> = {
  as?: T;
  children: ReactNode;
} & ComponentPropsWithoutRef<T>;

const Container = <C extends ElementType>({
  as,
  children,
}: ContainerProps<C>) => {
  const Component = as || "div";
  return <Component>{children}</Component>;
};

export default Container;

 

 

 

ComponentPropsWithoutRef<T>속성 덕분에 

...props도 사용할 수 있게 되었다. 

 

 

 

모든 속성이 사용 가능하므로 onClick 속성도 사용 가능하다 

 

        <Container
          as={Button}
          onClick={() => {
            console.log("clicked!");
          }}
        >
          Click Me
        </Container>

 

 

 

 

 

재활용 가능한  Card 컴포넌트

 

이런방식으로 

 

Card 컴포넌트도 재활용할 수 있게 된다 

 actions라는 추가적 컴포넌트를 포함해서.

 

 

 

import { ReactNode } from 'react';

type CardProps = {
  title: string;
  children: ReactNode;
  // "actions" is like an extra "slot" of this component
  // It's the same type as the children prop, since we expect JSX code as a prop value
  actions: ReactNode;
};

export function Card({ title, children, actions }: CardProps) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
      {actions}
    </section>
  );
}

// Example Usage:
export function Demo() {
  return (
    <Card
      title="My Card"
      actions={
        <button onClick={() => console.log('Button clicked!')}>
          Click Me!
        </button>
      }
    >
      <p>Some content</p>
    </Card>
  );
}