JAN's History
ReactNode, ReactElement, JSX.Element 완전 정복 (feat. 바벨과 createElement까지) 본문
ReactNode, ReactElement, JSX.Element 완전 정복 (feat. 바벨과 createElement까지)
JANNNNNN 2025. 4. 22. 16:10리액트 + 타입스크립트를 사용하다보면 ReactNode, ReactElement, JSX.Element 같은 타입이 자주 등장하죠..
이게 처음엔 꽤나 헷갈립니다.ㅠㅠ
이 글에서는 이 세 가지 타입이 각각 무엇인지, 어떤 상황에서 사용하는지, 그리고 실제 예시까지 함께 정리해보겠습니다.
이번 글에서는 초보자도 확실히 이해할 수 있도록 아래 내용을 순서대로 설명해볼게요
- JSX가 어떻게 동작하는지 (React.createElement + Babel)
- ReactNode / ReactElement / JSX.Element의 차이
- 각각 언제 사용해야 하는지
- 예제 가득한 실전 코드
- 최종 비교 정리표
먼저, JSX는 어떻게 동작할까?
우리가 흔히 쓰는 JSX는 사실 자바스크립트 문법이 아닙니다.
브라우저는 JSX를 이해하지 못해요.
그래서 개발 도구는 Babel 같은 트랜스파일러(transpiler) 를 이용해 JSX를 자바스크립트 코드로 바꿔줍니다.
JSX → JS로 바뀌는 과정
예를 들어 이런 JSX가 있다면?
const element = <h1>Hello React</h1>;
Babel은 아래처럼 바꿔줍니다:
const element = React.createElement("h1", null, "Hello React");
즉, JSX는 결국 React.createElement라는 함수로 변환됩니다.
그럼 React.createElement가 뭐길래?
React.createElement는 React 컴포넌트나 태그(div, span, h1 등)를 JS 객체로 만들어주는 함수예요.
이 객체는 브라우저에 실제로 렌더링되기 전에 내부적으로 처리되는 React 요소(React Element) 입니다.
이제 타입 개념으로 넘어가자 !
1. ReactElement
- 정의: React.createElement()로 만들어지는 리액트 요소 (JSX 하나)
- 정확한 타입: ReactElement<P = any, T extends string | JSXElementConstructor<any> = string>
- 특징: JSX 하나만 들어올 수 있음
const title: ReactElement = <h1>제목</h1>; //OK
const text: ReactElement = "hello"; //XX 오류
언제 쓰나요?
- JSX 하나만 받는 상황 / 컴포넌트의 리턴 타입을 JSX 하나로 제한할 때
2. JSX.Element
- 정의: TypeScript가 JSX 표현식에 부여하는 타입
- 내부적으로: ReactElement<any, any>와 거의 동일
- 쓰는 곳: 함수형 컴포넌트의 반환 타입 명시할 때
function Header(): JSX.Element {
return <h1>헤더</h1>;
}
언제 쓰나요?
- 컴포넌트가 JSX를 반환할 때 리턴 타입으로 명시
3. ReactNode
- 정의: React에서 렌더링 가능한 모든 타입을 포함하는 가장 넓은 범위의 타입
- 정확한 타입:
type ReactNode =
| ReactElement
| string
| number
| boolean
| null
| undefined
| ReactNode[];
type Props = {
children: ReactNode;
};
<Box>Hello</Box> ✅ 문자열
<Box>{123}</Box> ✅ 숫자
<Box>{false}</Box> ✅ boolean
<Box>{null}</Box> ✅ null
<Box><h1>태그</h1></Box> ✅ JSX
<Box>{[<p>1</p>, <p>2</p>]}</Box> ✅ 배열
언제 쓰나요?
- children prop으로 모든 걸 받아야 할 때 (텍스트, 숫자, JSX 등)
실제 예제 비교
🔹 ReactNode 사용 예시
type BoxProps = {
children: ReactNode;
};
function Box({ children }: BoxProps) {
return <div>{children}</div>;
}
<Box>안녕하세요</Box> ✅ 문자열
<Box>{42}</Box> ✅ 숫자
<Box>{false}</Box> ✅ boolean
<Box>{null}</Box> ✅ null
<Box><span>JSX</span></Box> ✅ JSX
🔹 ReactElement 사용 예시
type CardProps = {
children: ReactElement;
};
function Card({ children }: CardProps) {
return <div className="card">{children}</div>;
}
<Card><h2>제목</h2></Card> ✅ OK
<Card>제목</Card> ❌ Error (문자열은 안 됨)
🔹 JSX.Element 사용 예시
function Title(): JSX.Element {
return <h1>타이틀입니다</h1>;
}
render props 패턴과 함께 심층 파악해보기!
먼저, “render props 패턴”이란
render props 패턴은 컴포넌트에게 함수를 prop으로 넘겨서, 그 함수를 통해 어떻게 렌더링할지를 부모가 직접 정하는 방식입니다.
예시
❌ 일반적인 방식
function UserCard({ name }: { name: string }) {
return <div>{name}</div>;
}
<UserCard name="철수" />
☑️ render props 방식:
type UserCardProps = {
render: () => JSX.Element;
};
function UserCard({ render }: UserCardProps) {
return <div className="user-card">{render()}</div>;
}
<UserCard render={() => <strong>철수</strong>} />
즉, 내부에서 어떤 UI를 렌더링할지는 부모가 정해주는 거예요!
(이런 걸 '렌더링 전략을 외부에서 주입한다'고도 표현해요)
왜 이게 유용할까?
- 재사용성 증가: UserCard가 어떤 내용을 렌더링할지 전혀 몰라도 됨.
- 유연한 UI 구성: 필요한 만큼 props.render()로 원하는 JSX를 렌더링 가능.
- 상태 기반 제어가 쉬움: render 함수에 상태도 넘길 수 있음.
JSX.Element와 무슨 관계야?
우리가 넘기는 render 함수는 JSX를 반환해야 하죠!
그래서 이런 타입을 쓰는 거예요
type Props = {
render: () => JSX.Element;
};
즉, render라는 prop은 JSX 하나를 반환하는 함수가 돼요.
그 결과는 결국 React.createElement(...)로 변환돼서 렌더링돼요.
예제 1: render props + JSX.Element
type ProfileCardProps = {
title: string;
renderDetails: () => JSX.Element;
};
function ProfileCard({ title, renderDetails }: ProfileCardProps) {
return (
<div>
<h2>{title}</h2>
<div>{renderDetails()}</div>
</div>
);
}
// 사용 예시
<ProfileCard
title="홍길동"
renderDetails={() => (
<ul>
<li>나이: 24</li>
<li>직업: 프론트엔드</li>
</ul>
)}
/>
renderDetails는 () => JSX.Element 타입이므로 JSX를 하나 반환해야 해요.
예제 2: ReactNode로 더 유연하게
만약 함수가 아니라 그냥 JSX 자체를 넘기고 싶다면?
type ProfileCardProps = {
title: string;
details: ReactNode;
};
function ProfileCard({ title, details }: ProfileCardProps) {
return (
<div>
<h2>{title}</h2>
<div>{details}</div>
</div>
);
}
// 사용 예시
<ProfileCard
title="홍길동"
details={
<ul>
<li>나이: 24</li>
<li>직업: 프론트엔드</li>
</ul>
}
/>
예제 3 : 동적인 예제
type ToggleProps = {
on: boolean;
render: (on: boolean) => JSX.Element;
};
function Toggle({ on, render }: ToggleProps) {
return <div>{render(on)}</div>;
}
// 사용
<Toggle
on={true}
render={(on) =>
on ? <span>켜짐</span> : <span>꺼짐</span>
}
/>
요약 비교 정리표
구분 | 포함 내용 | 주요 사용처 |
ReactNode | JSX, 문자열, 숫자, null, 배열, boolean 등 | 다양한 children을 받을 때 |
ReactElement | JSX 하나 (React.createElement의 결과) | JSX 하나만 받는 prop / 내부 로직 |
JSX.Element | JSX 반환값 타입 (ReactElement와 유사) | 함수형 컴포넌트 반환 타입 |
실무 팁 & 권장사항
- children 받는 컴포넌트는 무조건 ReactNode로!
- ReactElement는 여러 값 허용 X → 정확한 제어가 필요할 때만 사용
- JSX.Element는 함수 컴포넌트의 반환 타입에 주로 사용
React에서 타입을 잘 이해하는 건 컴포넌트를 정확하게 설계하고, 실수를 줄이는 데 큰 도움이 되죠!
특히 TypeScript와 함께 쓰면 ReactNode, ReactElement, JSX.Element의 역할을 잘 구분해서 써야 하는데 꽤 헷갈리네요 ..
이 글로 개념이 좀 더 또렷해졌다면, 꼭 직접 몇 가지 컴포넌트를 만들어 보면서 연습해보세요!
'React' 카테고리의 다른 글
SPA와 MPA의 차이점 완전 정리 (0) | 2025.04.20 |
---|---|
브라우저의 기본 렌더링 방식과 React.js의 렌더링 방식 살펴보기(With Virtual DOM) (0) | 2025.04.06 |