ReactNode vs. JSX.Element 그리고 ReactElement
해당 글 작성한 시점의 React는 18.0, TypeScript는 2.8 버전이다.
ReactNode vs. JSX.Element
ReactNode, JSX.Element 모두 외부에서 주입받을 컴포넌트의 타입을 정의할 때 가장 많이 사용한다.
ReactNode
type ReactNode =
| ReactElement
| string
| number
| ReactFragment
| ReactPortal
| boolean
| null
| undefined
type ReactFragment = Iterable<ReactNode>
ReactNode는 ReactElement를 비롯하여 대부분의 자바스크립트 데이터 타입을 아우르는 범용적인 타입이다. 따라서 어떤 props을 받을 건데, 구체적으로 어떤 타입이 올지 알 수 없거나, 어떠한 타입도 모두 받고 싶다면 ReactNode로 지정해주는 것이 좋다.
참고로 ReactText와 ReactChild는 React를 사용할 때 큰 관련이 없기 때문에 곧 deprecated 될 것이라 명시되어 있다.
Blog.tsx
type BlogProps = {
profile: React.ReactNode
introduction: JSX.Element
}
const Blog = ({ profile, introduction }: BlogProps) => {
return (
<div>
{profile}
{introduction}
</div>
)
}
export default Blog
App.tsx
const App = () => {
return (
<Blog
profile={'howdy-mj'}
introduction={'howdy-mj'} // TS2322: Type 'string' is not assignable to type 'Element'.
/>
)
}
export default App
profile
에는 string을 선언할 수 있지만, introduction
은 string이기 때문에 Element 타입에 선언할 수 없다는 에러가 뜬다.
의아했던 점은, ReactNode에는 ReactElement만 있다는 것이다. 보통 ReactElement와 JSX.Element를 많이 비교하고 있기 때문에 두 개를 같이 아우를줄 알았는데 아니었다.
공통점부터 알아보자면, 두 가지 모두 React.createElement()
의 리턴 값이다.
React.createElement()
const HowdyMj = () => {
return <div>howdy-mj</div>
}
위와 같이 JSX로 작성된 코드를 자바스크립트로 변환하면 아래와 같다.
const HowdyMj = () => {
return React.createElement('div', null, 'howdy-mj')
}
다른 변환 예시를 보고 싶다면 Babel compiler - online에서 직접 해볼 수 있다.
사실 이렇게만 보면 구체적으로 와닿지 않는다. 위의 컴포넌트를 console로 찍어보자.
console.log(HowdyMj())
그렇다면 위처럼 type, key, props 등을 갖고 있는 것을 알 수 있다.
type은 해당 요소의 HTML 태그를 나타내고, props는 children이나 className, style 등의 속성을 나타낸다.
이걸 React.createElement()
로 만든다면 아래와 같다.
const HowdyMj = () => {
return React.createElement('div', {
children: 'howdy-mj',
})
}
이처럼 JSX는 React.createElement()
보다 훨씬 간단하게 컴포넌트를 만들 수 있다. 그래서 JSX가 React.createElement()
함수에 대한 문법적 설탕을 제공한다고 하는 것 같다.
ReactElement와 JSX.Element
자 이제 둘의 리턴 타입인 React.createElement()
에 대해 어느정도 숙지했다. 그렇다면 ReactElement와 JSX.Element의 차이점은 무엇일까?
ReactElement
ReactElement는 ReactElementType.js에서 flow로 정의되어 있어 쉽게 볼 수 있다.
export type ReactElement = {|
$$typeof: any,
type: any,
key: any,
ref: any,
props: any,
// ReactFiber
_owner: any,
// __DEV__
_store: { validated: boolean, ... },
_self: React$Element<any>,
_shadowChildren: any,
_source: Source,
|}
위에서 이미 본 익숙한 형태의 타입을 볼 수 있다.
interface ReactElement<
P = any,
T extends string | JSXElementConstructor<any> =
| string
| JSXElementConstructor<any>
> {
type: T
props: P
key: Key | null
}
type JSXElementConstructor<P> =
| ((props: P) => ReactElement<any, any> | null)
| (new (props: P) => Component<any, any>)
type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>
type Key = string | number
따라서 type이 받는 T 제너릭은 해당 HTML 태그의 타입을 받고, props는 그 외의 컴포넌트가 갖고 있는 속성을 받는다.
JSX.Element
JSX.Element는 ReactElement의 타입과 props를 모두 any로 받아 확장한 인터페이스다. 따라서 더 범용적으로 사용할 수 있다.
// Global
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> {}
}
}
// React Elements
declare namespace React {
// ... 생략
}
또한 React 관련 타입은 모두 React의 namespace에서 선언되었는데, JSX는 global namespace로 선언되어 있다. 따라서 React 내에서 JSX를 import하지 않아도 바로 사용이 가능하다.
참고