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
로 사용하자.잘못되었거나 보완할 있는 부분을 발견하신다면 댓글로 알려주시면 감사하겠습니다. :)