React Portal

신승준·2022년 10월 29일
0
post-thumbnail

문제

모달이나 사이드바와 같이 사용자 화면에 툭 튀어나오는 컴포넌트를 개발할 때, 부모의 CSS 속성으로 인해 문제가 발생할 수 있다. 부모 컴포넌트의 CSS 속성으로 인해 정중앙 혹은 맨 왼쪽에 위치시키지 못할 수도 있기 때문이다.

import React from 'react';
import styled from 'styled-components';

function App() {
	return (
		<Wrap>
			<Modal>나는 Modal</Modal>
		</Wrap>
	);
}

export default App;

const Wrap = styled.main`
	width: 400px;
	height: 400px;
`;

const Modal = styled.div`
	margin: 80px auto;
	border: 2px solid red;
	background-color: yellow;
	text-align: center;
	width: 300px;
`;

물론 위 경우 다음처럼 Modal을 꾸미면 상관없긴 하다. ㅎㅎㅎ 밑과 같이 하면 정중앙에 위치하긴 함!

const Modal = styled.div`
	/* margin: 80px auto; */
	border: 2px solid red;
	background-color: yellow;
	text-align: center;
	width: 300px;

	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
`;

그래도 css로부터 어떤게 종속될지 모르지 않는가? ㅎㅎㅎ 만약 Wrap의 styled에 다음과 같이 작성되어 있다면 Modal의 border-radius는 50%가 되는 걸 피할 수 없을 것이다. 이러한 예외 상황도 피하고 안전하게 딱 정중앙에 오게 하려면 어떻게 해야할까? Modal이 속해 있는 App이라는 놈을 벗어나야 하지 않을까?

import React from 'react';
import styled from 'styled-components';

function App() {
	return (
		<Wrap>
			<Modal>나는 Modal</Modal>
		</Wrap>
	);
}

export default App;

const Wrap = styled.main`
	width: 400px;
	height: 400px;

	div {
		border-radius: 50%;
	}
`;

const Modal = styled.div`
	/* margin: 80px auto; */
	border: 2px solid red;
	background-color: yellow;
	text-align: center;
	width: 300px;

	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
`;

Wrap의 div 태그에 border-radius를 50%로 하는 설정 때문에 Modal도 영향을 받아 둥그레졌다...




Portal

이를 해결하기 위해서는 어떻게 해야할까? 독립적으로 위치시키면 되지 않을까? 이럴 때 사용하는 것이 React의 Portal이다.

Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최적의 방법을 제공합니다.

라고 React 공식 문서에 적혀져 있다. 여기서 부모 컴포넌트의 DOM 계층 구조가 뭘까? public 폴더에 있는 index.html의 <div id='root'></div>라고 보면 될 것 같다. 여기서부터 React 컴포넌트들의 계층이 시작되지 않는가? 따라서 위의 말은 이 root라는 id를 가진 div 태그 바깥에 컴포넌트를 렌더링 시킬 수 있는 방법이 Portal이라는 것이다. 난 말로는 잘 이해하지 못하는 성격이라 직접 해보았다.

일단 Modal을 다른 파일로 분리시키자. 그리고 다음과 같이 작성해보자.
Modal.jsx

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

const Modal = () => {
	const content = <ModalWrap>나는 Modal</ModalWrap>;

	return ReactDOM.createPortal(content, document.getElementById('modal'));
};

export default Modal;

const ModalWrap = styled.div`
	border: 2px solid red;
	background-color: yellow;
	text-align: center;
	width: 300px;

	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
`;

App.jsx

import React from 'react';
import styled from 'styled-components';
import Modal from './Modal';

function App() {
	return (
		<Wrap>
			<Modal />
		</Wrap>
	);
}

export default App;

const Wrap = styled.main`
	width: 400px;
	height: 400px;

	div {
		border-radius: 50%;
	}
`;

portal을 사용하는 방법은 위와 같다. Modal 파일을 보자. 일단 ReactDOM을 react-dom으로부터 가져온다. 그리고 ReactDOM.createPortal(렌더링할 것, 어디에?)를 return하면 된다. 여기서 특이해 보일 수 있는 것이 document.getElementById('modal')이다. 직접적으로 DOM에 접근해서 거기다가 렌더링 하는 것인데, 이 modal이라는 id를 가진 태그는 public의 index.html에 만들면 된다!

즉 이렇게 함으로써 App이 포함된 root 계층이 아니라 modal 계층에 Modal을 렌더링할 수 있게 하는 것이다. 완전히 다른 포탈로 이동시킨 셈! 따라서 코드 상 Modal의 위치는 App 컴포넌트 안에 있는 반면, DOM에서의 위치는 root 계층의 App 컴포넌트 안이 아니라 다른 계층인 modal 안에 있는 것이다. 그렇기에 당연히 App의 style 속성을 계승 받지 않아 다음과 같이 border-radius: 50%도 피해갈 수 있다.

그냥 결론적으로 부모의 style을 종속 받지 않고 싶으면 index.html에 새로운 계층을 만들고 portal을 사용하여 그 새로운 계층에 렌더링한다고 생각하면 된다.


그러면 이벤트 버블링은 우째... 됩니까...?

portal이 DOM 트리의 어디에도 존재할 수 있다 하더라도 모든 다른 면에서 일반적인 React 자식처럼 동작합니다. context와 같은 기능은 자식이 portal이든지 아니든지 상관없이 정확하게 같게 동작합니다. 이는 DOM 트리에서의 위치에 상관없이 portal은 여전히 React 트리에 존재하기 때문입니다.

갸꿀! 실제로는 React DOM 트리 안에 Modal이 위치하기 때문에 이벤트 버블링이 portal을 사용하지 않았을 때와 동일하게 작동한다고 한다.

import React from 'react';
import styled from 'styled-components';
import Modal from './Modal';

function App() {
	return (
		<Wrap onClick={() => console.log('hello!')}>
			<div>
				<Modal />
			</div>
		</Wrap>
	);
}

export default App;

const Wrap = styled.main`
	width: 400px;
	height: 400px;

	div {
		border-radius: 50%;
	}
`;

위와 같이 작성 후 Modal을 클릭하면 브라우저 console에 다음과 같이 제대로 hello가 뜨는 것을 확인할 수 있다.

어쨋든 Portal 요놈은 원하는 컴포넌트를 다른 계층으로 이동시켜준다!




참고

React Portals
김정환님 블로그 : 리액트 Portal

profile
메타몽 닮음 :) email: alohajune22@gmail.com

0개의 댓글