리액트 엘리먼트의 타입을 지정할 때 자주 쓰이는 3가지 타입이다.
아래 타입들 쓸 때마다 헷갈렸는데(ReactNode
가 다 포함해서 ReactNode
를 썼다) 3가지 타입에 대해 알아보자
먼저 JSX.Element
다. Element
을 타고 index.d.ts
파일로 가보면 Element
의 타입이 지정되어 있는 것을 볼 수 있다.
여기서 재밌는 점은 Element
가 ReactElement
로 확장되어 있다는 것
즉, 아래와 같이 JSX.Element
와 React.ReactElement
는 같은 타입을 바라보고 있다.
그럼 ReactNode
를 보자.
ReactNode
는 대부분의 타입을 커버한다. ReactElement
까지 포함하는 것을 확인할 수 있다.
이제 아래와 같이 평범한 컴포넌트를 확인해보자.
타입스크립트는 리턴 타입을 JSX.Element
로 추론한다.
그렇담 JSX
의 타입은 무엇인가. React
이 namespace
를 살펴보자. index.d.ts
에 들어가보면 namespace React
가 있다.
그리고 대략 4천줄 쯤에 JSX
가 있는데, 이 namespace
안에 Element
가 또 있다!
Element
는 GlobalJSXElement
라는 타입을 확장하고, GlobalJSXElement
가 뭔지 타고 들어가보면
그런데 GlobalJSXElement
는 다시 JSX.Element
를 확장하고 있다.(순환참조아녀?)
아무튼 정리하자면 React.JSX.Element
는 결국 JSX.Element
와 같음을 알 수 있다.
이제 다음 컴포넌트를 보자. 리턴 타입이 ReactNode
인 컴포넌트는 대부분의 타입을 포함하기 때문에 아래처럼 string
을 리턴해도 컴파일 에러가 나지 않는다.
그리고 마지막으로 리턴 타입이 ReactElement
인 경우를 보면 앞서 말했든 JSX.Element
와 ReactElement
은 같기 때문에 ReactElement
도 오직 JSX
만 리턴할 수 있다.
결론은 JSX.Element
와 ReactElement
같고 ReactNode
는 더 넓은 타입이다.
흔히 사용하는 Select
와 Option
컴포넌트다.
Option
은 OptionType
타입을 강제하고 있다.
OptionType
에는 __brand
라는 프로퍼티가 있는데, __brand
는 간단하게 말해 개발자가 지정한 타입 외 다른 타입의 호환을 막는 역할을 한다.
하지만, 이렇게 만든 Select
과 Option
을 사용하려고 하면 에러가 나온다
as OptionType
으로 타입을 강제했음에도 Option
컴포넌트가 ReactElement
타입을 반환하여 타입이 불일치 한다는 것이다.
언뜻 보기에 OptionType
잘 반환하는 거 같은데 뭐가 문제일까?
먼저, <Option>
컴포넌트를 변수에 할당해보자
할당된 변수의 타입은 JSX.Element
로 나오고 있다. 즉, 제대로 추론을 못하고 있다는 뜻
하지만 컴포넌트가 아닌 함수로 호출하게 되면 제대로 타입을 추론한다.
그래서 문제를 해결할 수 있는 방법은 컴포넌트를 함수로 호출하는 것이다.
하지만 JSX 엘리먼트를 함수로 호출하는 건 아주 잘못된 행동이다.
리액트가 컴포넌트 인스턴스로 인식하지 못하여 Reconciliation를 수행하지 못하기 때문에 매번 호출해버린다.
아쉽게도 children
props에 강제로 타입을 지정하는 방법은 없다.
리액트는 모든 JSX 요소를 JSX.Element
로 추론하며, 커스텀 타입을 무시하기 때문!
참고 : total typescript