JSX의 제한 사항[요구 사항]
React는 JSX로 작업한다.
JSX는 component에서 반환되는 코드로 React는 이 코드를 실제 DOM으로 렌더링한다.
하지만 JSX에는 제한 사항이 존재한다.
return (
<h2>Hi there!</h2>
<p>This does not work :~(</p>
) ;
만약 위의 코드처럼 component의 return
값에 복수의 JSX 요소들이 있다면 오류가 발생한다.
즉, root-level에서는 두 개의 JSX 요소가 인접해 있다면 다른 JSX 요소에 의해서 반환되거나 변수에 저장되지 않는다.
React에서는 일반적으로 root JSX 요소가 하나여야만 한다.
저장하거나 반환하는 가장 위의 요소는 반드시 하나여야 한다.
그 이유는 명확한데, JavaScript에서는 둘 이상의 값을 return
시킬 수 없다.
물론 배열의 형태로는 가능하지만, 배열 역시 하나의 새로운 값을 가진 객체일 뿐이다.
배열도 여전히 하나만 return
되며 배열 두개를 동시에 반환하는 것은 아니다.
JSX의 원리
JSX 코드는 실질적으로 React.createElement
로 변환되어 사용되는데 단 한개의 호출만 return
되어야 한다.
결국 React.createElement
도 JavaScript 함수이기 때문에 return
시에는 하나만 반환할 수 있다.
return (
React.createElement('h2', {}, 'Hi there!') ;
React.createElement('p', {}, 'This does not work :~(') ;
) ;
JavaScript의 태생적인 한계 때문에 root-JSX 요소는 하나만 있어야 한다.
그러면 이 문제를 어떻게 해결할까?
해결방법들
div로 감싸기
return (
<div>
<h2>Hi there!</h2>
<p>This does not work :~(</p>
</div>
) ;
<div>
로 감싸면 1개만 반환하게 된다. 배열에 넣어서 반환하는 것과 비슷한 효과를 볼 수 있다.
사실 <div>
일 필요는 없다. 어떤 요소를 사용해도 상관없으며, 심지어 사용자 지정 component를 사용해도 무관하다.
중요한 것은 return
되는 값은 하나여야 한다는 것이다.
물론 다른 해결 방법도 존재한다.
Native JavaScript Array
Native는 실행 환경에 종속되지 않는 ECMA Script 명세의 내장 객체를 뜻한다. 자주 쓰이는 Native JavaScript는 아래와 같다.
- String()
- Number()
- Boolean()
- Array()
- Object()
- function()
- RegExp()
- Date()
- Error()
- Symbol()
그 중에서 Array, 즉 배열을 이용하여 하나로 return
이 가능하다.
return ([
<h2>Hi there!</h2>,
<p>This does not work :~(</p>
]) ;
그런데 조금 문제가 있다.
배열을 반환할 수 있는 이유는 React가 JSX요소의 배열을 다룰 수 있기 때문인데 실제로 사용해보면 Warning이 발생한다.
왜냐하면 JSX 요소인 배열로 작업할 때마다 React는 모든 요소에 대한 key
를 필요로 한다.
data list 안에서 동적으로 mapping
하고 그 data들을 JSX요소에 mapping
하려면 key
가 필요하다.
하드 코딩된 JSX 요소 배열이라면 다음과 같이 간단히 key
를 추가할 수 있다.
return ([
<h2 key="header">Hi there!</h2>,
<p key="state">This does not work :~(</p>
]) ;
일반적으로 이렇게 하지는 않지만 이 방법으로 한다고 한들 문제될 것은 없다.
이렇게 key
를 추가하고 배열로 감싸는건 귀찮기 때문에 <div>
를 추가하는 방식이 훨씬 많이 사용된다.
이렇게 <div>
로 감쌀 때, 그 빈도가 중첩된다면 새로운 문제를 발생시킨다.
div soup
...
<div>
<div>
<div>
<div>
<h2>Some content - yeah, this can really happen.</h2>
</div>
</div>
</div>
</div>
...
조금 과장해서 표현하긴 했지만 실제 DOM을 렌더링될 때 React component가 많이 중첩될 수 있다.
그러한 component들은 여러가지 이유로 <div>
로 감싸질 것이다.
이렇게 불필요하게 감싼 <div>
들이 모두 실제 DOM으로 렌더링 된다.
이것은 꽤나 흔하게 일어나는 일로 크기가 큰 어플리케이션의 경우 불필요한 <div>
가 더 많을 수 있다.
그것들에는 감쌀 것이 필요했기 때문이다.
그것이 실질적으로 페이지에서 어떤 의미도 갖지 않는데도 말이다.
이것은 그리 좋은 관행이 아니다.
너무 많은 HTML 요소를 렌더링하게 되면 궁극적으로는 애플리케이션이 느려지게 될 것이다.
브라우저는 모든 요소들을 렌더링해야하며, React는 변경된 compoenent들을 확인해야할 것이다.
따라서, 불필요한 내용을 렌더링하는 것은 일반적으로 프로그래밍에서 좋은 생각이 아니며, 효율을 떨어트린다.
Wrapper component
약간의 속임수를 써보자. Wrapper라고 하는 새로운 component를 만들었다.
const Wrapper = (props) => props.children;
export default Wrapper;
기본적으로 빈 component이며 그저 하는 일이라고 하면 props.children
을 반환하는 것뿐이다.
하지만 JSX의 요구 사항을 충분히 충족시켜준다.
<div>
대신에 Wrapper만 작성해주면 끝이다.
당연하게도 이 component는 DOM에 아무것도 렌더링하지 않는다
props.children
을 사용하면 tag 내부의 값들을 가르키는 것으로 component가 있으나 없으나 변화가 없는게 당연하다.
약간의 눈속임으로 문제도 해결하고 어플리케이션의 퍼포먼스에도 영향을 끼치지 않으니 괜찮은 해결방법이라고 생각된다.
div soup에 빠지지 않고 너무나도 편리한 Wrapper component.
이 component는 직접 만들 필요가 없다. 이미 React와 함께 제공된다.
Fragment
Fragment component라고 불리며 다음과 같이 접근 가능하다.
import React from 'react';
return (
<React.Fragment>
<h2>Hi there!</h2>
<p>This does not work :~(</p>
</React.Fragment>
) ;
import { Fragment } from 'react';
return (
<Fragment>
<h2>Hi there!</h2>
<p>This does not work :~(</p>
</Fragment>
) ;
짧은 구문
return (
<>
<h2>Hi there!</h2>
<p>This does not work :~(</p>
</>
) ;
<> </>
가 key
나 props
를 지원하지 않는다는 점을 제외하면 다른 요소를 사용하는 것과 동일한 방식으로 사용할 수 있습니다.
이번 시간에는 JSX가 가진 고질적인 문제와 그 해결 방법에 대해서 알아봤다.
한가지 놀라웠던 것은 React에서도 해당 문제를 인지했는지 Fragment로 component를 지원한다는 것이었다.