React Portal

kim98111·2021년 12월 7일
1

React

목록 보기
9/29
post-thumbnail

React Portal

"React Portal"컴포넌트를 우리가 원하는 돔 엘리먼트(실제 돔 엘리먼트)에 렌더링하고 싶은 경우에 사용하는 개념입니다.

// index.js

import React from 'react';
import ReactDOM from 'react-dom';

// index.html의 root라는 요소에 모든 컴포넌트를 렌더링
ReactDOM.render(<App />, document.getElementById('root'));

위 코드처럼 지금까지는 ReactDOM.render을 사용하여 <div id="root"></div>라는 최상위 돔 엘리먼트 안에 "모든 리액트 컴포넌트를 렌더링"했습니다. 하지만 Modal이나 Dialogue 같은 컴포넌트를 렌더링할 때는 이러한 구조를 벗어나 "또 다른 최상위 돔 엘리먼트에 위치"시키도록 하고 싶은 경우가 있습니다.

<div id="root"></div> 최상위 돔 엘리먼트에 렌더링하는 것은 기술적으로 잘못된 부분은 아니지만 이상적이지 않습니다. HTML 문서는 Semantic하게 작성해야 하는데 이러한 관점에서는 이상적이지 않다고 판단이 됩니다.

Modal을 예로 들면 Modal은 Overlay로써 논리적으로 "모든 것 위에" 있다고 말할 수 있습니다. 하지만 Modal을 나타내는 컴포넌트가 다른 컴포넌트 내부에 깊숙히 중첩되어 렌더링되는 경우 의미가 맞지 않다고 판단됩니다.

이는 리액트의 "Portal" 개념을 사용하여 컴포넌트는 "다른 돔 엘리먼트 아래에 렌더링"하여 해결할 수 있습니다. "다른 돔 엘리먼트 아래에 렌더링"만할 뿐이지 구조적으로 상위 컴포넌트에서 props를 통해 데이터를 전달받는 것은 이전과 동일하게 동작합니다.

Portal 사용하기

"Portal"은 react-dom 라이브러리를 사용해야 하며, HTML 문서에서 컴포넌트를 렌더링할 돔 루트 엘리먼트(root 엘리먼트가 아닌 엘리먼트)가 필요합니다.

참고로 react 라이브러리는 상태 관리 등을 비롯한 리액트의 모든 기능이 존재하는 라이브러리 이며, react-dom 라이브러리는 리액트를 사용해 로직과 각종 기능들을 웹 브라우저와 연결시켜 줍니다. 즉, DOM과 호환되도록 만들어주는 역할을 합니다.

// index.html
<!DOCTYPE html>
<html lang="ko">
    <head>
    ,,,,
    <heady>
    
    <body>
        <!-- 백드롭이 렌더링될 위치 -->
        <div id="backdrop"></div>
        
        <!-- 모달이 렌더링될 위치 -->
        <div id="overlay"></div>
        
        <!-- 일반적인 컴포넌트들이 렌더링될 위치 -->
        <div id="root"></div>
    </body>
</html> 
// ErrorModal.jsx
import React from 'react';
import ReactDOM from 'react-dom';

// 백드롭 컴포넌트
const Backdrop = props => {
    return <div onClick={props.onConfirm}></div>;
};

// 모달 컴포넌트
const Modal = props => {
    return <div></div>;
};

// 에러 모달 컴포넌트
const ErrorModal = props => {
    return (
        <>
            // Backdrop 컴포넌트를 backdrop 엘리먼트 아래에 렌더링
            {ReactDOM.createPortal(<Backdrop />, document.getElementById('backdrop')}
            // Modal 컴포넌트를 modal 엘리먼트 아래에 렌더링
            {ReactDOM.createPortal(<Modal />, document.getElementById('modal')};
        </>
    );
};

export default ErrorModal;

ReactDOM.createPortal 메서드는 두 개인 인수를 전달받습니다. 첫 번째 인수로는 "JSX 문법"을 전달하고, 두 번째 인수로는 컴포넌트를 렌더링할 위치인 "돔 요소 노드"를 전달합니다.

ErrorModal 컴포넌트가 렌더링될 때 Backdrop 컴포넌트와 Modal 컴포넌트는 ReactDOM.createPortal 메서드의 두 번재 인수로 전달된 돔 엘리먼트 아래에 렌더링됩니다.

이후에 ErrorModal 컴포넌트를 "어디서 사용하던 언제나" HTML 문서(index.html) 기준으로 Backdrop 컴포넌트는 <div id="backdrop"> </div>의 Content에 렌더링되고, Modal 컴포넌트는 <div id="modal"></div>의 Content에 렌더링됩니다.

즉, ReactDOM.createPortal 메서드를 사용하여 컴포넌트를 우리가 원하는 "특정 HTML 엘리먼트로 옮길 수 있습니다".

createRoot

React 18부터는 더이상 react-dom 패키지의 render을 사용하지 않고, react-dom/client 패키지의 createRoot를 사용해야 합니다.

자세한 내용은 여기에 나와있습니다.

// react 17v
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

위 코드는 18v 이전에 작성하던 코드이며 아래는 18v 이후 코드입니다.

// react 18v
import { createRoot } from 'react-dom/client';
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

react-dom/client 패키지의 createRoot 메서드에 인수로 루트 돔 엘리먼트를 전달하고, 반환되는 객체의 render 메서드에 루트 컴포넌트를 전달해줍니다.

profile
Frontend Dev

0개의 댓글