Skip to content

ReactNode, ReactElement, JSX.element, ComponentType의 차이에 대해서 #126

@justhighway

Description

@justhighway

1. 모든 것의 시작: React.createElementReactElement

React 타입을 이해하기 위해서는 JSX가 실제로 어떻게 동작하는지 알아야 합니다.

우리가 작성하는 JSX 코드는 브라우저가 직접 이해할 수 없습니다. 빌드 과정에서 Babel, SWC와 같은 트랜스파일러가 JSX 코드를 순수한 JS 파일로 변환시키고, 이 JS 파일을 브라우저가 읽어들이는 방식으로 동작합니다.

JSX 코드

const Greeting = () => {
  return <div className='greeting'>Hello, World!</div>
};

트랜스파일링 후 JS 코드

const Greeting = () => {
  return React.createElement(
    'div',
    { className: 'greeting' },
    'Hello, World!'
  );
};

여기서 핵심은 React.createElement()의 반환 값입니다. 이 함수는 화면에 렌더링될 내용을 설명하는 일반 JS 객체를 반환하는데, 이것이 바로 ReactElement 입니다.

console.log(<Greeting />)을 실행해보면 다음과 같은 객체를 확인할 수 있습니다.
아래 객체는 React가 DOM을 어떻게 그리고 업데이트해야 하는지에 대한 '설계도' 역할을 합니다.

Image
{
  '$$typeof': Symbol(react.transitional.element),
  type: [Function: Greeting],
  key: null,
  props: {},
  _owner: {
    name: 'Test',
    env: 'Server',
    key: null,
    owner: null,
    stack: [],
    props: { params: [Promise], searchParams:
 [Promise] },
    debugStack: [Error: react-stack-top-frame],
    debugTask: { run: [Function: run] }      
  },
  _store: {}
}

ReactElement의 타입

ReactElement의 타입 정의를 보면 다음과 같이 되어 있습니다.
렌더링될 요소의 type, 속성인 props, 그리고 리스트 렌더링 시 사용되는 key를 핵심 속성으로 가집니다.

여기서 핵심은, ReactElement는 React UI의 태그 하나를 설명하는 불변의 객체라는 점입니다.

interface ReactElement<
        P = unknown,
        T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>,
    > {
        type: T;
        props: P;
        key: string | null;
    }

2. 가장 흔한 반환 타입: JSX.Element

그렇다면 JSX.Element는 무엇일까요?
내외부적으로 ReactElement와 거의 동일하지만, 중요한 차이점이 있습니다.

JSX.Element의 타입 정의

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

위 코드를 보다시피, JSX.ElementReact.ReactElement를 확장한 타입입니다.
주목할 점은 JSXReact 네임스페이스가 아닌 전역(global) 네임스페이스에 선언되어 있다는 점입니다. 이 덕분에 우리가 리액트에서 JSX를 별도로 import 하지 않고도 타입으로 사용할 수가 있습니다.

즉, JSX.ElementReactElement와 내부 사양은 똑같지만 전역적으로 선언되어 있어 컴포넌트 반환 타입으로 편리하게 사용할 수 있습니다.

// 일반적인 컴포넌트 반환 타입 선언
function MyComponent(): JSX.Element {
  return <div>Hello</div>;
};

3. 모든 것을 품는 타입 React.ReactNode

ReactNode는 이름처럼 리액트가 화면에 렌더링할 수 있는 모든 종류의 Node를 포함하는 가장 넓은 범위의 타입입니다.

ReactNode 타입 정의

ReactNode는 다음과 같은 타입들을 모두 포함하는 Union 타입입니다.

type ReactNode =
        | ReactElement
        | string
        | number
        | bigint
        | Iterable<ReactNode>
        | ReactPortal
        | boolean
        | null
        | undefined
        | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES[
            keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES
        ]
        | Promise<AwaitedReactNode>;

언제 사용할까요?

ReactNode는 주로 컴포넌트의 props 타입을 정의할 때 사용합니다.
다양한 타입을 포함하기 때문에 children 등 외부에서 주입받는 prop을 다뤄야 할 때 유연하게 적용해주기 위해 사용합니다.

interface CardProps {
  children: React.ReactNode; // Card 컴포넌트 사이에 어떤 것이 와도 됨
};

function Card ({ title, children }: CardProps): JSX.Element {
  return (
    <div className="card">
      <div>{children}</div>
    </div>
  );
};

위 예시처럼 children의 타입으로 ReactNode를 사용하면 문자열, 숫자, 리액트 컴포넌트, 심지어 null까지 다양한 형태의 자식을 받을 수 있습니다.

즉, ReactNodechildren prop이나 다양한 종류의 콘텐츠를 받아야 하는 prop에 사용하면 가장 적합하다고 볼 수 있습니다. JSX.Element는 오직 React 요소만 허용하지만, ReactNode는 원시 값(string, number) 등도 허용합니다.

4. 최종 정리: 그래서 언제 무엇을 써야 할까요?

타입 설명 주요 사용 사례
JSX.Element (ReactElemet) React.createElement의 반환 객체 타입 컴포넌트의 반환 값 타입을 명시할 때.

React.ReactNode|React가 렌더링할 수 있는 모든 것(JSX, string, number, null 등)을 포함|children 이나 다양한 콘텐츠를 허용해야 하는 prop의 타입을 정의할 때

JSX.Element vs. ReactElement

둘 중 하나를 쓴다면 JSX.Element가 더 모던 웹 개발에 적합하고 일반적으로 권장됩니다.

1. 간결함

JSX.Element는 전역(global) JSX 네임스페이스에 선언되어 있어 별도의 import 구문 없이 바로 사용할 수 있는 반면, React.ReactElement를 사용하려면 React를 import해야 합니다.

// 👍 간결하고 일반적인 방식
const ComponentA = (): JSX.Element => {
  return <div>Hello</div>;
};

// 👎 React 모듈을 import 해야함
import { ReactElement } from 'react';

const ComponentB = (): ReactElement => {
  return <div>Hello</div>;
};

2. 커뮤니티 표준 (Community Standard) 🤝

React와 TypeScript를 함께 사용하는 커뮤니티에서는 컴포넌트의 반환 타입을 명시할 때 JSX.Element를 사용하는 것이 사실상의 표준(de facto standard)으로 자리 잡았습니다. 대부분의 오픈소스 라이브러리와 예제 코드에서 이 방식을 따르고 있는데, 관례를 지키면 코드를 일관성 있고 다른 개발자가 이해하기 쉽게 만듭니다.

3. ReactElement를 확장한 타입

기술적으로 JSX.Element React.ReactElement<any, any>를 extends한 타입입니다. 컴포넌트 반환 값을 타이핑하는 실용적인 목적에서는 두 타입이 가리키는 대상이 거의 같으므로, 더 편리하고 표준화된 JSX.Element를 사용하는 것이 합리적입니다.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions