[React] ReactNode vs ReactElement vs ReactChild vs JSX.element

YeongMin·2022년 5월 21일
5

React

목록 보기
1/2

1. 시작점

React 에서 TypeScript 를 이용하여 children Component 를 감싸주는 Wrap 역활의 Component의 props에 다른 Component를 children 으로 받아 사용하려고 하니 Type을 명시해주라는 에러가 발생하였다.

급하게 구글형님과 스택형님의 힘을 빌려 React.ReactNode 라는 Type 을 지정해주면 해결된다고 한 답변이 있었고 React.ReactElement 로 지정을 해주라는 답변 또한 있었다.

그래서 도대체 이것들이 뭐길래 이 둘을 추천해주는거지? 라는 생각이 이 포스트의 시작점이다.

React.ReactNode

대부분 이 타입으로 지정을 해주면 typescript ERROR를 피할 수 있었기에 JSX 문법을 사용하여 생성된 컴포넌트, 태그들을 return 받을 때 무의식적으로 사용해 주었다.

그 이유는 JSX.element, ReactElement, ReactChild 를 포함하는 상위개념이었기 때문이었다.
그래서 처음으로 React의 타입을 까보기로 했다. 😎

그렇게 찾은 녀석..

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

뭐가 되게 많다..

차근차근 보니 string number boolean null undefined 등 자바스크립트의 원시타입을 포함하고 있었고 ReactElement, ReactFragment, ReactPortal 이 보였다.

이 포스트는 시작점이 ReactElement vs ReactNode 였기 때문에 바로 ReactElement 로 넘어가 보겠다. 💨

React.ReactElement

자 다음녀석인 ReactElement 를 보겠다.

우선 이녀석은 cretaeElement 함수를 통해 생성된 객체에 지정된 타입이었다. (자세한건 넘어가겠다. 포스트의 본질은 아니니!)

	// ReactElement 를 반환한다.
    function createElement<P extends {}>(
        type: FunctionComponent<P> | ComponentClass<P> | string,
        props?: Attributes & P | null,
        ...children: ReactNode[]): ReactElement<P>;
    interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
        type: T;
        props: P;
        key: Key | null;
    }

솔직히 타입스크립트에 대해 확실히 공부한 상태가 아니라 조금 어렵다..😲

확실한건 ReactElementcreateElement 함수를 통해 반환받은 객체가 될 수 있으며 type, props, key가 존재한다는 것이다.

그리고 "ReactNode 와 달리 원시 타입을 허용하지 않고 완성된 jsx 요소만을 하용한다." 라고 알고 다음으로 넘어가자. (다른 분의 블로그를 참고했다.)

그래서 둘의 차이는 ❓

우리가 직접적으로 사용할 때는 그렇게 큰 차이가 있지않다.
ReactNode 는 결국 ReactElement원시타입 그리고 추가적으로 몇개의 interface를 포함 하고 있는 큰 범위의 타입이다.

그래서 아래 코드와 같이 Children Component 를 만든 후 Wrapper Componentprops 로 넘겨주게 됬을 때 타입스크립트에서는 별 문제없이 진행되는 것을 볼 수 있다.

여기서 children 의 타입을 React.ReactElement 로 변경을 해보겠다.

역시 특별한 문제가 없이 정상적으로 작동한다.

..
..

그러나!! 여기서 children 으로 컴포넌트 대신 원시타입(string) 을 넣게 된다면❓

Error

요약: 내가 허용하는 타입은 완성된 JSX 요소, 즉 쉽게 이야기해 Component 만 가능하다!! 라고 알려준다.
..
..

반면에 ReactNode는 역시 원시타입을 허용하는 Type 이기 때문에 아무런 문제가 없다.

  • ReactNodeReactElement 의 큰 차이점은 javascript 의 원시타입을 허용하느냐 안하느냐 의 차이이다.

2. 추가적인 이야기

2.1 클래스형과 함수형 컴포넌트 리턴 타입은 다르다?

클래스형 컴포넌트

React.ReactNode Type을 기본적으로 return 한다.

함수형 컴포넌트

React.ReactElement interface를 기본적으로 return 한다.

2.2 JSX 문법을 통한 element 생성은 어떻게 되는걸까?

HTML 처럼 태그를 작성하는데 분명 React 와 HTML 은 다르다.
JSX 문법은 도대체 어떻게 태그를 만들고 안의 내용을 집어넣는걸까?


// before
<div name="ahn0min">Hello, {name}</div>


// after
React.createElement("div", {
  name : "ahn0min", }, "Hello ", name);

createElement 에 내가 입력한 것들을 인자로 받아 생성하는 것이었다!

2.3 종종 등장하는 ReactText, ReactChild

  • 존재는 하지만 실제로 React는 이 타입을 사용하지 않는다.
  • 즉 실제로 사용되는 것은 ReactNode, ReactElement, JSX.Element 정도이다.

3. 결론

  • 포함관계 : ReactNode > ReactChild > ReactNode (ReactNode가 모든 것을 포함한다.)

  • ReactNond 타입은 컴포넌트, 원시타입, boolean, null, undefined 등 대부분을 허용한다.

  • ReactChild 타입은 ReactElement, ReactText (string, number) 를 포함한다.

  • ReactElement 타입은 React.createElement 함수를 통해 완성된 객체(컴포넌트)만을 허용하는 타입이다.

  • JSX.Element 타입은 ReactElement 를 상속받은 interface이며 별 차이가 없다.

  • 클래스형 컴포넌트는 React.ReactNode type 을 기본적으로 return 받는다.

  • 함수형 컴포넌트는 React.Element interface를 기본적으로 return 받는다.

앞으로 이렇게 하겠습니다 ! 🤞

  • 컴포넌트를 자식으로 받아서 사용하려고 하는 경우에는 앞으로는 대부분을 포함하는 ReactNode 가 아닌 정확한 interface인 ReactElement로 사용하자.

Reference


잘못되었거나 보완할 있는 부분을 발견하신다면 댓글로 알려주시면 감사하겠습니다. :)

profile
Front-End 안영민

2개의 댓글

comment-user-thumbnail
2023년 2월 16일
  1. 결론에서 포함관계에 ReactNode가 두 개네요!
답글 달기
comment-user-thumbnail
2023년 7월 17일

감사해요 ✨

답글 달기