React 에서 TypeScript 를 이용하여 children Component 를 감싸주는 Wrap 역활의 Component의 props에 다른 Component를 children 으로 받아 사용하려고 하니 Type을 명시해주라는 에러가 발생하였다.
급하게 구글형님과 스택형님의 힘을 빌려 React.ReactNode 라는 Type 을 지정해주면 해결된다고 한 답변이 있었고 React.ReactElement 로 지정을 해주라는 답변 또한 있었다.
그래서 도대체 이것들이 뭐길래 이 둘을 추천해주는거지? 라는 생각이 이 포스트의 시작점이다.
대부분 이 타입으로 지정을 해주면 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 로 넘어가 보겠다. 💨
자 다음녀석인 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;
}
솔직히 타입스크립트에 대해 확실히 공부한 상태가 아니라 조금 어렵다..😲
확실한건 ReactElement 는 createElement 함수를 통해 반환받은 객체가 될 수 있으며 type, props, key가 존재한다는 것이다.
그리고 "ReactNode 와 달리 원시 타입을 허용하지 않고 완성된 jsx 요소만을 하용한다." 라고 알고 다음으로 넘어가자. (다른 분의 블로그를 참고했다.)
우리가 직접적으로 사용할 때는 그렇게 큰 차이가 있지않다.
ReactNode 는 결국 ReactElement 과 원시타입 그리고 추가적으로 몇개의 interface를 포함 하고 있는 큰 범위의 타입이다.
그래서 아래 코드와 같이 Children Component 를 만든 후 Wrapper Component 의 props 로 넘겨주게 됬을 때 타입스크립트에서는 별 문제없이 진행되는 것을 볼 수 있다.

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

역시 특별한 문제가 없이 정상적으로 작동한다.
..
..
그러나!! 여기서 children 으로 컴포넌트 대신 원시타입(string) 을 넣게 된다면❓
Error

요약: 내가 허용하는 타입은 완성된 JSX 요소, 즉 쉽게 이야기해 Component 만 가능하다!! 라고 알려준다.
..
..
반면에 ReactNode는 역시 원시타입을 허용하는 Type 이기 때문에 아무런 문제가 없다.

ReactNode 와 ReactElement 의 큰 차이점은 javascript 의 원시타입을 허용하느냐 안하느냐 의 차이이다.React.ReactNode Type을 기본적으로 return 한다.
React.ReactElement interface를 기본적으로 return 한다.
HTML 처럼 태그를 작성하는데 분명 React 와 HTML 은 다르다.
JSX 문법은 도대체 어떻게 태그를 만들고 안의 내용을 집어넣는걸까?
// before
<div name="ahn0min">Hello, {name}</div>
// after
React.createElement("div", {
name : "ahn0min", }, "Hello ", name);
createElement 에 내가 입력한 것들을 인자로 받아 생성하는 것이었다!

- 존재는 하지만 실제로 React는 이 타입을 사용하지 않는다.
- 즉 실제로 사용되는 것은 ReactNode, ReactElement, JSX.Element 정도이다.
포함관계 : 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로 사용하자.잘못되었거나 보완할 있는 부분을 발견하신다면 댓글로 알려주시면 감사하겠습니다. :)
