HTEMLElement를 컴포넌트의 props로 넘겨줄 일이 생겼다. 그 이유는 웹에 일정한 틀에 컴포넌트들을 담아주고 싶었기 때문이다. 내가 만들고 있어 웹페이지는 간단한 todolist라서 큰 웹 전체를 사용할 필요가 없어서 모든 페이지에서 공용으로 사용할 프레임 컴포넌트가 필요했다.프레임 컴포넌트를 제작할 때는 모든 페이지에서 사용해야하므로 재사용성을 잘 생각해서 짜야한다.
interface IBaseFrame {
children: React.ReactNode;
}
const BaseFrame: React.FC<IBaseFrame> = ({ children }) => {
return <div className={styles.container}>{children}</div>;
};
const ToDoList = () => {
return (
<BaseFrame>
<CheckboxList/>
<ToDoInput/>
</BaseFrame>
);
};
ToDoList 컴포넌트를 보면 BaseFrame안에 CheckboxList와 ToDoInput이 들어있는데 이렇게 작성을 하면 BaseFrame의 children prop으로 자동넘어가게 된다.
그리고 BaseFrame 컴포넌트는 children을 항상 받아야하므로 Interface로 props type을 정해줬다. children의 타입은 React.ReactNode이다.
컴포넌트 타입으로 올 수 있는 타입들은 JSX.Element, ReactElement, ReactNode 이렇게 세개가 있는데 클래스형 컴포넌트는 render 메소드에서 ReactNode를 리턴하고 함수형 컴포넌트는 ReactElement를 리턴한다.
바벨에 의해서 JSX는 React.createElement로 변환이 된다.
React.createElement의 리턴 타입이 ReactElement, JSX.Element이다.
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;
}
type ReactNode = ReactElement | string | number | ReactFragment | ReactPortal | boolean | null | undefined;
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> { }
interface ElementClass extends React.Component<any> {
render(): React.ReactNode;
}
...
타입을 분석을 해보면 JSX.Element는 React.Element를 상속받고 ReactNode는 ReactElement이외에도 null등 다른 타입을 가질 수 있다.
따라서 ReactNode <= ReactElement <= JSX.Element 이와 같은 관계를 가진다는 것을 알 수 있다.
그렇다면 왜 children 타입으로 ReactNode를 사용했나?
그 이유는 ReactElement는 null, undefined가 될 수 없기 때문이다. 프레임으로 사용하는 컴포넌트이긴 하지만 자식으로 아무것도 안들어올 수 있는 경우도 있을 것이기 때문에 null, undefined가 될 수 있는 ReactNode로 타입을 지정했다.
배운점
항상 컴포넌트 타입을 지정할 때 어떤 타입을 써야할지 헷갈렸는데 직접 타입 내부 코드를 들여다 봄으로써 근본(?)부터 공부할 수 있어서 재밌었다. 제대로 집고 넘어갈 수 있어서 속이 시원했다.
참고
https://ko.reactjs.org/docs/composition-vs-inheritance.html
https://simsimjae.tistory.com/426