main-logo

React 자식 컴포넌트 props 타입 정의(element)

ReactNode, ReactElement, JSX.Element에 대해 알아보기

profile
sunny
2023년 01월 28일 · 0 분 소요

들어가며

React children 컴포넌트 요소의 props 타입을 정의할 때 ReactNode, ReactElement, JSX.Element를 적절히 사용 하고 계시나요?
다른 사람의 소스 코드를 보다가 한 번도 사용하지 않았던 JSX.Element를 마주하게 되면서 제 코드를 확인해보니 ReactNode, ReactElement 두 가지를 혼용해서 작업하고 있었습니다. 지금까지는 다행스럽게도(?) 3가지를 코드를 넣었을 때 큰 오류는 없었지만, ReactNode, ReactElement, JSX.Element의 차이가 궁금해져서 알아보게 되었습니다.

 

ReactNode

React를 환경에서 작업하면 아래 경로의 숨겨진 파일에 타입이 정의되어 있습니다. node_modules/@types/react/index.d.ts 파일 내 소스 코드를 확인해보면 아래와 같습니다.

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

ReactNode는 jsx에서 사용하는 모든 요소의 타입에 대해 정의되어 있습니다.
null, undefined까지 정의되어 있어 children 컴포넌트가 구체적으로 어떤 타입이 오는지 불확실한 경우에 정의할 수 있습니다.

ReactElement

ReactElement는 createElement 함수를 통해 생성되는 type, props를 가진 객체의 형태입니다. 우리는 거의 jsx로 코드를 작성하기 때문에 사용할 일은 없지만, createElement는 태그(div, span), ReactComponent, React Fragment로 구성된 type 인자와 props 인자로 구성됩니다.

React.createElement(type, [props], [...children]);
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
  type: T;
  props: P;
  key: Key | null;
}

그래서 ReactElement는 반환된 객체 형태로 텍스트로 props를 넘길 경우 컴파일 오류가 나지만, ReactFragment 요소로 값을 넘길 경우는 ReactElement로 타입 정의를 해도 좋습니다.

type Props = {
  type: ReactElement,
};

const ChildrenComponent = ({ type }: Props) => {
  return <div>ChildrenComponent : {type}</div>;
};

const ParentComponent = () => {
  return (
    <div>
      {/* ts(2322) : 'string' 형식은 'ReactElement<any, string | JSXElementConstructor<any>>'형식에 할당할 수 없습니다. */}
      <ChildrenComponent type="test" />
      {/* Error fix */}
      <ChildrenComponent type={<>test</>} />
    </div>
  );
};

export default ParentComponent;

JSX.Element

JSX.Element는 아래 정의된 내용으로 보면 ReactElement의 type, props이 모두 any로 정의된 타입입니다. 제너릭 유형을 갖는 type, props를 확장한 인터페이스 형태로 범용적으로 사용할 수 있습니다.

namespace JSX {
  interface Element extends React.ReactElement<any, any> { }
}

JSX.Element가 ReactElement의 확장된 인터페이스 타입을 가지므로 동일하게 텍스트로 props를 넘길 경우 컴파일 오류를 내지만, ReactFragment로 감싸줘서 오류를 해결할 수 있습니다.

type Props = {
  type: JSX.Element,
};

const ChildrenComponent = ({ type }: Props) => {
  return <div>ChildrenComponent : {type}</div>;
};

const ParentComponent = () => {
  return (
    <div>
      {/* ts(2322) : 'string' 형식은 'Element' 형식에 할당할 수 없습니다. */}
      <ChildrenComponent type="test" />
      {/* Error fix */}
      <ChildrenComponent type={<>test</>} />
    </div>
  );
};

export default ParentComponent;

 

 마치며

아직 React, typescript가 익숙지 않아서 잘못 사용하거나 정확히 이해하지 못하고 작업하는 경우가 많습니다. 그래서 컴파일 에러가 나지 않은 이상 제대로 사용하고 있는 지조차 알기가 싶지 않은데요, 이번 기회에 공부할 수 있어서 제게도 좋은 기회였습니다. 다른 분들에도 도움이 되었으면 좋겠습니다.

읽어주셔서 감사합니다.

참고문서