클래스형 컴포넌트는 render메소드에서 ReactNode
를 리턴한다. 함수형 컴포넌트는 ReactElement
를 리턴한다.
JSX는 바벨에 의해서 React.createElement(component, props, ...children) 함수로 트랜스파일된다.
html 처럼 생긴 문법을 리액트 라이브러리의 렌더링 함수로 변환하는것이다. 그래서 JSX를 사용하지 않고도 리액트를 사용할 수 있으나(JSX없이 사용하는 React) 이렇게 하면 매우 불편하다.
// jsx
<div>Hello {this.props.toWhat}</div>
<Hello toWhat="World" />
JSX는 아래 처럼 변경된다.
// transpile
React.createElement('div', null, `Hello ${this.props.toWhat}`);
React.createElement(Hello, {toWhat: 'World'}, null);
이 React.createElement의 리턴 타입이 바로 ReactElement
와 JSX.Element
이다.
ReactElement와 JSX.Element는 묶어서 생각해도 좋다. JSX.Element의 제네릭 인터페이스가 ReactElement이다.
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;
}
React.createElement를 호출하면 이런 타입의 객체가 리턴된다. 그냥 리액트 컴포넌트를 JSON 형태로 표현해놨다고 생각하자.
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
ReactNode는 ReactElement의 superset이다. ReactNode는 ReactElement일 수 있고 null, undefined, boolean 등등.. 좀 더 유연한 타입 정의라고 할 수 있다.
왜 클래스형 컴포넌트의 render()는 ReactNode를 리턴하고 함수형 컴포넌트는 ReactElement를 리턴하는가?
historical reasons이라고 한다.
ReactNode이외에는 null타입을 가지지 않으므로 ReactElement를 리턴하는 함수형 컴포넌트에서는 아래와 같이 null을 union해줘야한다.
const example = (): ReactElement | null => {
if(/* true조건 */) return null;
return <p>Hello World</p>;
};