Portal
은 부모 컴포넌트 DOM 계층 구조 바깥에 있는 특정 DOM으로 자식을 렌더링 시켜주는 방법을 제공함ReactDOM.createPortal(child, container)
child: Element,문자열,Fragment 같은 렌더링할 수 있는 React 자식
container: 넣으려는 DOM Element
render() {
// 새로운 div를 생성하지 않고 `domNode` 안에 자식을 렌더링함
return ReactDOM.createPortal(
this.props.children,
domNode
);
}
JSX 컴포넌트는 React.createElement 로 변환되며 반드시 하나만 가짐
루트 JSX 컴포넌트는 반드시 하나여야 하고 자식 노드는 반드시 안에 포함된다.
<div>
나 <>
등으로 감싸서 하나로 만들 수 있지만 불필요하게 너무 많아질 수 있음
의존관계 떄문에 그러지 않고 싶어도 특정 dom 내부 깊숙히 위치하게 되는 노드가 있게 된다.
modal 같은 것들도 특정 이벤트로 발생하게 되기에 특정 컴포넌트 안에서 prop을 받아야 하는 상태임
포탈이 어디 이상한 DOM 노드에 존재해도 다른 일반적인 react 컴포넌트 처럼 동작한다.
DOM트리의 위치와 상관없이 포탈은 해당되는 react 트리 내에 존재하는 것으로 치기 때문임
눈에 보이는 DOM트리와 React의 Virtual DOM 트리는 구분되어있다.
따라서 React트리에서는 부모 컴포넌트로부터 전파되는 이벤트나 프로퍼티를 적절하게 얻을 수 있게 된다.
portal의 전형적인 유스케이스는 부모 컴포넌트에 overflow: hidden이나 z-index가 있는 경우이지만, 시각적으로 자식을 “튀어나오도록” 보여야 하는 경우도 있습니다. 예를 들어, 다이얼로그, 호버카드나 툴팁과 같은 것입니다.
부모 밑에 존재할 때 z-index, overflow:hidden 같은 속성이 있어 방해를 받아 제 역할을 못할 여지가 있기 때문이다
<div id="modal-root"></div>
index.html
에 이런식으로 순간이동 시킬 위치를 지정해주었다.
export default function App() {
const [visible, setVisible] = useState(false);
return (
<div className="App">
<h1>Hello Portal</h1>
<p>모달모달</p>
<button onClick={() => setVisible(true)}>모달열기</button>
<Modal visible={visible} onClose={() => setVisible(false)}>
<h2>안녕나는모달이야</h2>
<button onClick={() => setVisible(false)}>닫을랭</button>
</Modal>
</div>
);
}
위처럼 <App/>
의 특정 돔 내부에 위치하지만 실제로는 그렇지 않다.
덤으로 useClickAway
hook을 만들어서 모달 바깥을 클릭할 때 닫힐 수 있는 이벤트도 처리해주었다.