-
Notifications
You must be signed in to change notification settings - Fork 3
Description
1. 모든 것의 시작: React.createElement와 ReactElement
React 타입을 이해하기 위해서는 JSX가 실제로 어떻게 동작하는지 알아야 합니다.
우리가 작성하는 JSX 코드는 브라우저가 직접 이해할 수 없습니다. 빌드 과정에서 Babel, SWC와 같은 트랜스파일러가 JSX 코드를 순수한 JS 파일로 변환시키고, 이 JS 파일을 브라우저가 읽어들이는 방식으로 동작합니다.
JSX 코드
const Greeting = () => {
return <div className='greeting'>Hello, World!</div>
};트랜스파일링 후 JS 코드
const Greeting = () => {
return React.createElement(
'div',
{ className: 'greeting' },
'Hello, World!'
);
};여기서 핵심은 React.createElement()의 반환 값입니다. 이 함수는 화면에 렌더링될 내용을 설명하는 일반 JS 객체를 반환하는데, 이것이 바로 ReactElement 입니다.
console.log(<Greeting />)을 실행해보면 다음과 같은 객체를 확인할 수 있습니다.
아래 객체는 React가 DOM을 어떻게 그리고 업데이트해야 하는지에 대한 '설계도' 역할을 합니다.
{
'$$typeof': Symbol(react.transitional.element),
type: [Function: Greeting],
key: null,
props: {},
_owner: {
name: 'Test',
env: 'Server',
key: null,
owner: null,
stack: [],
props: { params: [Promise], searchParams:
[Promise] },
debugStack: [Error: react-stack-top-frame],
debugTask: { run: [Function: run] }
},
_store: {}
}ReactElement의 타입
ReactElement의 타입 정의를 보면 다음과 같이 되어 있습니다.
렌더링될 요소의 type, 속성인 props, 그리고 리스트 렌더링 시 사용되는 key를 핵심 속성으로 가집니다.
여기서 핵심은, ReactElement는 React UI의 태그 하나를 설명하는 불변의 객체라는 점입니다.
interface ReactElement<
P = unknown,
T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>,
> {
type: T;
props: P;
key: string | null;
}2. 가장 흔한 반환 타입: JSX.Element
그렇다면 JSX.Element는 무엇일까요?
내외부적으로 ReactElement와 거의 동일하지만, 중요한 차이점이 있습니다.
JSX.Element의 타입 정의
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> {}
}
}위 코드를 보다시피, JSX.Element는 React.ReactElement를 확장한 타입입니다.
주목할 점은 JSX가 React 네임스페이스가 아닌 전역(global) 네임스페이스에 선언되어 있다는 점입니다. 이 덕분에 우리가 리액트에서 JSX를 별도로 import 하지 않고도 타입으로 사용할 수가 있습니다.
즉, JSX.Element는 ReactElement와 내부 사양은 똑같지만 전역적으로 선언되어 있어 컴포넌트 반환 타입으로 편리하게 사용할 수 있습니다.
// 일반적인 컴포넌트 반환 타입 선언
function MyComponent(): JSX.Element {
return <div>Hello</div>;
};3. 모든 것을 품는 타입 React.ReactNode
ReactNode는 이름처럼 리액트가 화면에 렌더링할 수 있는 모든 종류의 Node를 포함하는 가장 넓은 범위의 타입입니다.
ReactNode 타입 정의
ReactNode는 다음과 같은 타입들을 모두 포함하는 Union 타입입니다.
type ReactNode =
| ReactElement
| string
| number
| bigint
| Iterable<ReactNode>
| ReactPortal
| boolean
| null
| undefined
| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES[
keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES
]
| Promise<AwaitedReactNode>;언제 사용할까요?
ReactNode는 주로 컴포넌트의 props 타입을 정의할 때 사용합니다.
다양한 타입을 포함하기 때문에 children 등 외부에서 주입받는 prop을 다뤄야 할 때 유연하게 적용해주기 위해 사용합니다.
interface CardProps {
children: React.ReactNode; // Card 컴포넌트 사이에 어떤 것이 와도 됨
};
function Card ({ title, children }: CardProps): JSX.Element {
return (
<div className="card">
<div>{children}</div>
</div>
);
};위 예시처럼 children의 타입으로 ReactNode를 사용하면 문자열, 숫자, 리액트 컴포넌트, 심지어 null까지 다양한 형태의 자식을 받을 수 있습니다.
즉, ReactNode는 children prop이나 다양한 종류의 콘텐츠를 받아야 하는 prop에 사용하면 가장 적합하다고 볼 수 있습니다. JSX.Element는 오직 React 요소만 허용하지만, ReactNode는 원시 값(string, number) 등도 허용합니다.
4. 최종 정리: 그래서 언제 무엇을 써야 할까요?
| 타입 | 설명 | 주요 사용 사례 |
|---|---|---|
JSX.Element (ReactElemet) |
React.createElement의 반환 객체 타입 |
컴포넌트의 반환 값 타입을 명시할 때. |
React.ReactNode|React가 렌더링할 수 있는 모든 것(JSX, string, number, null 등)을 포함|children 이나 다양한 콘텐츠를 허용해야 하는 prop의 타입을 정의할 때
JSX.Element vs. ReactElement
둘 중 하나를 쓴다면 JSX.Element가 더 모던 웹 개발에 적합하고 일반적으로 권장됩니다.
1. 간결함
JSX.Element는 전역(global) JSX 네임스페이스에 선언되어 있어 별도의 import 구문 없이 바로 사용할 수 있는 반면, React.ReactElement를 사용하려면 React를 import해야 합니다.
// 👍 간결하고 일반적인 방식
const ComponentA = (): JSX.Element => {
return <div>Hello</div>;
};
// 👎 React 모듈을 import 해야함
import { ReactElement } from 'react';
const ComponentB = (): ReactElement => {
return <div>Hello</div>;
};2. 커뮤니티 표준 (Community Standard) 🤝
React와 TypeScript를 함께 사용하는 커뮤니티에서는 컴포넌트의 반환 타입을 명시할 때 JSX.Element를 사용하는 것이 사실상의 표준(de facto standard)으로 자리 잡았습니다. 대부분의 오픈소스 라이브러리와 예제 코드에서 이 방식을 따르고 있는데, 관례를 지키면 코드를 일관성 있고 다른 개발자가 이해하기 쉽게 만듭니다.
3. ReactElement를 확장한 타입
기술적으로 JSX.Element는 React.ReactElement<any, any>를 extends한 타입입니다. 컴포넌트 반환 값을 타이핑하는 실용적인 목적에서는 두 타입이 가리키는 대상이 거의 같으므로, 더 편리하고 표준화된 JSX.Element를 사용하는 것이 합리적입니다.