유용한 리액트 컴포넌트 타입

박희수·2024년 6월 2일
0

🙋‍♀️ 이 포스트는 '우아한 타입스크립트 with React' 도서를 읽고 적은 글입니다.


오늘은 타입스크립트를 사용할 때 헷갈렸던 타입의 용도, 그리고 실제 프로젝트 개발시 사용하면 좋을 것 같은 유용한 타입들에 대해 정리하도록 하겠습니다. 😋

1. 헷갈리는 타입 React.ReactElement , JSX.Element, React.ReactNode

✋ 자세한 설명 전에, 기본 배경 지식에 대해 알아보고 갑시다.

  • 클래스형 컴포넌트는 render 메소드에서 ReactNode를 리턴하고 함수형 컴포넌트는 ReactElement를 리턴합니다.
  • JSX는 바벨에 의해서 React.createElement함수로 트랜스파일 됩니다. html처럼 생긴 문법을 리액트 라이브러리의 렌더링 함수로 변환하는 것입니다. 그래서 우리가 JSX를 사용해 UI를 편하게 그릴 수 있는데 이를 사용하지 않고도 UI를 그릴 수는 있습니다. 다만 매우 불편할 뿐입니다.

1-1) 세 타입의 차이점

🎈 
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
type ReactNode = | ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

위를 보면 ReactNode는 ReactElement를 포함한 ReactChild 외에도 boolean, null, undefined 등 넓은 버주의 타입을 포함합니다.
즉, ReactNode는 리액트의 render 함수가 반환할 수 있는 모든 형태를 담고 있다고 볼 수 있습니다.

🎈 
declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> {
     // ...
    }
    // ...
  }
}

JSX.Element는 ReactElement의 제네릭으로 props와 타입 필드에 대해 any 타입을 가지도록 확장하고 있습니다. 즉, JSX.Element는 ReactElement의 특정 타입으로 볼 수 있습니다.

ReactNode > ReactElement > JSX.Element
이 순서대로 범주가 넓다고 볼 수 있겠네요 🙆‍♀️

1-2) 사용 예시

// 📌 ReactNode 

interface MyComponentProps {
 children?: React.ReactNode;
}

ReactNode 타입은 리액트의 render 함수가 반환할 수 있는 모든 형태를 담고 있기 때문에, 리액트 컴포넌트가 가질 수 있는 모든 타입을 의미합니다.

chilren을 포함하는 props 타입을 선언할 때 자주 사용하곤 합니다.

// 📌 JSX.Element 

interface Props {
 icon : JSX.Element;
}

const Item = ({ icon } : Props) => {
	// prop으로 받은 컴포넌트의 props에 접근할 수 있습니다. 
    const iconSize = icon.props.size;
    
    return (<li>{icon}</li>);
}

// icon prop에는 JSX.Element 타입을 가진 요소만 할당할 수 있습니다.
const App = () => {
	return <Item icon={<Icon size={14} />} />
}

JSX.Element는 props와 타입 필드가 any 타입인 리액트 엘리먼트를 나타냅니다. 이러한 특성 때문에 리액트 엘리먼트를 prop으로 전달받아 render props 패터능로 컴포넌트를 구현할 때 유용합니다.

위 코드와 같이 icon prop을 JSX.Element 타입으로 선언함으로써 해당 prop에는 JSX 문법만 삽입할 수 있습니다.

🌼 또한 icon.props에 접근하여 prop으로 넘겨받은 컴포넌트의 상세한 데이터를 가져올 수 있습니다.

// 📌 ReactElement

interface IconProps {
 size : number;
}

interface Props {
 icon : React.ReactElement<IconProps>;
}

const Item = ({ icon } : Props) => {
 const iconSize = icon.props.size;
 
 return <li>{icon}</li>
}

JSX.Element 예시를 확장해 추론 관점에서 더 유용하게 활용할 수 있는 방법은 JSX.Element 대신 ReactElement를 사용하는 것입니다.

이때 원하는 컴포넌트의 props를 ReactElement의 제네릭으로 지정해줄 수 있습니다.
만약 JSX.Element가 ReactElement의 props 타입으로 any가 지정되었다면 ReactElement 타입을 활용해 제네릭에 직접 해당 컴포넌트의 props 타입을 명시해줍니다.

2. 리액트에서 기본 HTML 요소 타입 활용하기

리액트를 사용하면서 HTML button 태그를 확장한 Button 컴포넌트를 만들어본 경험이 있을 것이다.

const SquareButton = () => <button>정사각형 버튼</button>

이렇게 새롭게 만든 Button 컴포넌트는 기존 HTML button과 같은 역할을 하면서도 새로운 기능이나 UI가 추가된 형태이다.
기존의 button 태그가 클릭 이벤트를 등록하기 위한 onClick 이벤트 핸들러를 지원하는 것처럼, 새롭게 Button 컴포넌트도 onClick 이벤트 핸들러를 지원해야만 일관성과 편의성을 모두 챙길 수 있다.

DetailedHTMLProps와 ComponentWithoutRef
HTML 태그의 속성 타입을 활용하는 대표적인 2가지 방법은
리액트의 DetailedHTMLProps와 ComponentPropsWithoutRef 타입을 활용하는 것이다.

type NativeButtonProps = React.DetailHTMLProps<
	React.ButtonHTMLAttributes<HTMLButtonElement>,
    HTMLButtonElement
>;

type ButtonProps = {
	onClick?: NativeButtonProps['onClick'];
};

React.DetailePropsHTHMLProps를 활용하는 경우, 위와 같이 쉽게 HTML 태그 속성과 호환되는 타입을 선언할 수 있다.

type NativeBuottnType = React.ComponentPropsWithoutRef<'button'>;
type ButtonProps = {
	onClick?: NativeButtonType['onClick'];
};

React.ComponentPropsWithouRef 타입은 위와 같이 활용할 수 있다.

profile
프론트엔드 개발자입니다 :)

0개의 댓글