컴포넌트가 반환하는 엘리먼트들은 반드시 단 하나의 최상위 태그로 묶여있어야 합니다.
JSX 코드는 리액트의 createElement()로 변환되는데, 단 1개의 createElement() 호출만 반환되어야 합니다.
createElement('만들 요소', 만들 요소를 설정하는 객체, '시작 및 종료 태그 사이에 들어갈 컨텐츠')
2개 이상의 createElement 반환시 에러가 발생합니다.
function MyComponent = () {
return (
// Uncaught Error
<h1>Hello</h1>
<p>World</p>
)
}
import React from 'react'
function MyComponent = () {
return (
// Error
React.createElement('h2', {}, 'Hello'),
React.createElement('p', {}, 'World')
)
}
일반적으로 최상위 태그는 <div>를 사용합니다.
function MyComponent = () {
return (
<div>
<h1>Hello</h1>
<p>World</p>
</div>
)
}
import React from 'react'
function MyComponent = () {
return (
React.createElement(
'div',
{},
React.createElement('h2', {}, 'Hello'),
React.createElement('p', {}, 'World')
)
)
}
하지만 이렇게 불필요한 div를 사용하면 실제 DOM으로 렌더링될 대 리액트 컴포넌트가 의미 없는 div들로 중첩되는 div soup에 빠질 수 있습니다.
<div>
<div>
<div>
<div>
<h2>Hello World</h2>
</div>
</div>
</div>
</div>
불필요한 div는 중첩된 CSS 선택자를 사용한다면 div들의 스타일링을 깨뜨릴 수 있으며, 불필요한 HTML 요소들이 렌더링 되면서 어플리케이션도 느려질 수 있습니다.
React.Fragment를 사용하면 불필요한 div를 사용하지 않고 최상위 태그로 묶여있어야 하는 문제도 해결할 수 있습니다.
import React from 'react'
function MyComponent = () {
return (
<React.Fragment>
<h1>Hello</h1>
<p>World</p>
</React.Fragment>
)
}
워크플로우가 이를 지원할 때 아래와 같이 단순하게 사용할 수 있습니다.
import React from 'react'
function MyComponent = () {
return (
<>
<h1>Hello</h1>
<p>World</p>
</>
)
}
React.Fragment는 실제 HTML 요소를 실제 DOM에 렌더링하지 않는 Wrapper를 사용하는 것과 같습니다.
ReactDOM.createPortal(랜더링 되너야 하는 노드, 노드가 렌더링되어야 하는 실제 DOM의 컨테이너를 가리키는 포인터)
만약 modal 창을 사용할 때, 실제 DOM을 간결한 HTML 구조를 갖췄는지의 관점으로 보면 좋지 않습니다.
기본적으로 모달은 전체 페이지 위에 표시되는 오버레이이기 때문입니다.
return (
<>
<MyModal />
<MyInputForm />
</>
)
<section>
<div class="my-modal"> <!--모달-->
</div><!--모달-->
<form>
<input type='text' />
</form>
<Input />
</section>
)
리액트 포탈을 사용하면 오버레이 내용이 있는 모달을 깊게 중첩되지 않게 하면서 사용할 수 있습니다.
index.htmll에서 컴포넌트가 root div 위로 올 수 있도록 id를 가진 div를 만들어 줍니다.
<!-- index.html -->
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="backdrop-root"></div>
<div id="overlay-root"></div>
<div id="root"></div>
</body>
실제 렌더링 될 DOM의 위치를 바꾸고 싶은 요소들을 컴포넌트로 만듭니다.
ReactDOM.createPortal의 첫 번째 인수에 생성한 컴포넌트를 입력합니다.
두 번째 인수에 랜더링 될 위치의 html DOM의 id를 가져옵니다.
import React from "react";
import ReactDOM from "react-dom";
import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";
// Backdrop 컴포넌트
const Backdrop = (props) => {
return <div className={classes.backdrop} onClick={props.onConfirm} />;
};
// Modal 컴포넌트
const ModalOverlay = (props) => {
return (
<Card className={classes.modal}>
<header className={classes.header}>
<h2>{props.title}</h2>
</header>
<div className={classes.content}>
<p>{props.message}</p>
</div>
<footer className={classes.actions}>
<Button onClick={props.onConfirm}>Okay</Button>
</footer>
</Card>
);
};
const ErrorModal = (props) => {
return (
<React.Fragment>
{ReactDOM.createPortal(
<Backdrop onConfirm={props.onConfirm} />,
document.getElementById("backdrop-root")
)}
{ReactDOM.createPortal(
<ModalOverlay
title={props.title}
message={props.message}
onConfirm={props.onConfirm}
/>,
document.getElementById("overlay-root")
)}
</React.Fragment>
);
};
export default ErrorModal;
이렇게 React Portals을 사용하면 리액트 코드는 이전에 사용했던 것처럼 사용할 수 있으면서, HTML 코드는 이제 다른 위치에서 렌더링됩니다.