리액트 - 포탈

Hyeonseok Jeong·2022년 12월 18일
0

React

목록 보기
14/16

리액트 포탈

모달같은 컴포넌트는 모달 전체가 렌더링 되는 곳에 실제로 렌더링 되어서는 안된다.

const Modal = () => {
	<div>
		<header>Modal Header</header>
		<div>Modal body</div>
		<footer>Modal Footer</footer>
	</div>
}
const App = () => {
	<>
		{조건 && <Modal />}
		<div>
			Main Page
		</div>
	</>
}

실제 렌더링 되는 곳

<body>
	<div id='root'>
		<div>
			<header>Modal Header</header>
			<div>Modal body</div>
			<footer>Modal Footer</footer>
		</div>
		<div>
			Main Page
		</div>
	</div>
</body>

위와 같이 렌더링 되어서는 안된다. → 물론 렌더링 되었을때 위와 같이 되어도 사용자 UI에는 모달창이 보이지만 코드 적으로 이는 좋은 코드는 아니다.
프로젝트에서 Modal 컴포넌트가 전체 애플리케이션의 위쪽에 있지 않고 다른 컴포넌트 안에 깊이 중첩되어 있기도 하는데, 이렇게 다른 컴포넌트 안에 깊이 중첩 되어있을때 Modal 컴포넌트를 HTML의 의 직계 자식으로 이동 시켜야 되는데 이를 포탈로 해결할 수 있다.
포탈 사용법
포탈을 사용하기 위해서는 두 가지가 필요하다.
첫번째로는 컴포넌트를 이동시킬 장소가 필요하고 두번째로는 컴포넌트에게 그 곳에서 포털을 가져야 한다고 알려줄 필요가 있다.
이러한 방법을 사용하기 위해서 리액트의 Public 폴더에 존재하는 index.html 에 장소를 만들어 주어야한다.

<body>
	<div id='backdrop-root'> </div>
	<div id='overlay-root'> </div>
	<div id='root'></div>
</body>

위와 같이 여러 root를 만들어서 여러 다른 종류의 컴포넌트들을 해당 root들로 포털 되도록 할 수 있다.
생성한 장소에 컴포넌트 포털 시키기
예시 코드

import React from 'react'
import Card from './Card'
import Button from './Button'
import classes from './ErrorModal.module.css'

const ErrorModal = props => {
    return (
        <>
            <div className={classes.backdrop} onClick={props.onConfirm} />
            <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>
        </>
    )
}

export default ErrorModal

리액트에게 해당 코드가
<div className={classes.backdrop} onClick={props.onConfirm} />
실제로 어딘가로 포탈되어야 한다고 알려주어야 한다.
그러기 위해서는 새로운 컴포넌트가 될 상수를 추가하고 동일한 파일에 추가한다.
그 이유는 해당 앱에서 새로운 컴포넌트가 될 상수(이후 Backdrop 컴포넌트)는 모달과 함께 사용하기만 하면 되기 때문이다.
따라서 모든 컴포넌트를 하나의 큰 파일에 저장한다. 물론 여러 컴포넌트 파일로 나눠서 저장할 수 도있다.
특히 Backdrop을 다른 컴포넌트들과 함께 사용한다면 말이다.
(우선 예시 코드에서만 사용한다는 가정하에 진행)

import React from 'react'
import Card from './Card'
import Button from './Button'
import classes from './ErrorModal.module.css'

// 추가된 포털 코드
const Backdrop = props => {
	return <div className={classes.backdrop} onClick={props.onConfirm} />
};

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 (
        <>
            // backdorop 코드를 포털로 이동시키기 위해서 이동
						// <div className={classes.backdrop} onClick={props.onConfirm} />

						// ModalOverlay 코드를 포털로 이동시키기 위해서 이동
            //<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>
        </>
    )
}

export default ErrorModal

지금 위와 같이 한일은 Modal 컴포넌트를 두개의 개별 컴포넌트로 나눈 것이다.
Backdrop 과 ModalOverlay로 나누었다.
이렇게 해야 포탈을 이용하기 쉽기 때문이다.
이후 JSX 안에 프래그먼트가 남아있으므로 표현을 추가할 수 있다.
이제 메소드를 출력하기 위해서는 리액트에 정의되어 있지 않지만 리액트와 함께 제공되는 다른 라이브러리에 제공된 react-dom 라이브러리에 정의되어있는 기능을 사용해야한다.
리액트는 일종의 라이브러리라고 생각할 수 있는데 모든 리액트 기능, state 관리, 애드온, 베이크 인 등이 있는 라이브러리이다.
그리고 react-dom은 그런 로직들과 기능들을 웹 브라우저로 가져오기 위해 리액트를 사용한다.
즉, 그것들이 DOM에서의 작업들과 호환되도록 해주는 라이브러리 라는 것이다.
리액트 라이브러리 자체는 리액트를 DOM이 있는 환경에서 실행하는지 또는 네이티브 앱을 빌드하는 데 사용하는지에 대해 전혀 신경쓰지 않는다.
또한 예를 들어 리액트 네이티브를 리액트와 함께사용하여 네이티브 모바일 앱을 빌드 할 수있다.
react-dom은 브라우제에 대한 리액트용 어뎁터의 일종이라고 할 수있다.
따라서 무언가를 실제DOM의 다른 위치로 포탈을 보낼때 react-dom 에서 임포트 해야한다.

import React from 'react'
// 추가된 코드 - react-dom import 시 ReactDom 자리에는 원하는 이름을 적으면 된다.
import ReactDom from 'react-dom'

import Card from './Card'
import Button from './Button'
import classes from './ErrorModal.module.css'

// 추가된 포털 코드
const Backdrop = props => {
	return <div className={classes.backdrop} onClick={props.onConfirm} />
};

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 (
        <>
        </>
    )
}

export default ErrorModal
이제 위와같은 코드를 통해서 react-dom라이브러리를 이용한 코드를 작성하게 되면 ReaactDom에서 createPortal 메소드를 호출할 수 있다.
const ErrorModal = props => {
    return (
        <>
					{ReactDom.createPortal()}
        </>
    )
}

export default ErrorModal

createPortal 메소드는 두 개의 인수를 취하는데, 첫번째는 렌더링 되어야 하는 리액트 노드이다.
위의 코드들로 예시를 들면 Backdrop을 렌더링할 수 있다.

import React from 'react'
// 추가된 코드 - react-dom import 시 ReactDom 자리에는 원하는 이름을 적으면 된다.
import ReactDom from 'react-dom'

import Card from './Card'
import Button from './Button'
import classes from './ErrorModal.module.css'

// 추가된 포털 코드
const Backdrop = props => {
	return <div className={classes.backdrop} onClick={props.onConfirm} />
};

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 (
        <>
		{ReactDom.createPortal(<Backdrop onClick={props.onConfirm} />)}
        </>
    )
}

export default ErrorModal

기존에 전달되는 props를 또다시 전달해줘야한다는 사실을 잊지않고 Backdrop에서 사용되는 onClick 속성에 속성값을 잘 전달해주어야 한다.
createPortal 메소드의 두 번째 인수는 포인터 이다. 이 요소가 렌더링 되어야 하는 실제 DOM의 컨테이너를 가리키는 포인터이다.
현재 코드에서는 앞서 작성했던 index.html 에서 작성한 backdrop-root 컴포넌트로 렌더링이 되어야 하므로 두 번째 요소에 실제로 렌더링 되어야하는 요소를 적어야한다. DOM API를 이용해서 말이다.

const ErrorModal = props => {
    return (
        <>
			{
			// 첫번째 요소
			ReactDom.createPortal(<Backdrop onClick={props.onConfirm} />
			,
			// 두번째 요소
			document.getElementById('backdrop-root'))
			}
        </>
    )
}

export default ErrorModal

위와 같이 실제로 렌더링 되어야 하는 요소를 선택하여 DOM API를 이용하는데 예시로 document.getElementById 메소드를 이용해서 요소를 선택하여 실제 HTML DOM 요소에 접근할 수 있다.
위와 같이 사용되는 createPortal 메소드는 흔히 리액트 index.js 에서 사용되는 ReactDOM.createRoot(document.getElementById(‘root’)); 과는 다르게 리액트에 의해 이미 렌더링된 기존 애플리케이션 내부로 포털 시킨다. 렌더링하려는 HTML 내용을 다른 장소로 이동 시키는 것이다.
포털을 시키면

<body>
	<div id='root'>
		<div>
			<header>Modal Header</header>
			<div>Modal body</div>
			<footer>Modal Footer</footer>
		</div>
		<div>
			Main Page
		</div>
	</div>
</body>

위 코드와 같이 modal및 backdrop이 root요소안에 같이 렌더링 되는게 아니라

<body>
	<div id="backdrop-root">
    	<div>
			<header>Modal Header</header>
			<div>Modal body</div>
			<footer>Modal Footer</footer>
		</div>
    </div>
	<div id="root">
    	<div>
			Main Page
		</div>
    </div>
</body>

위 코드와 같이 backdrop-root 요소 밑에 렌더링 되게 된다.
즉, body요소의 직계 자식이 된다는 것이다.

  • 포털의 핵심은 렌더링된 HTML 내용을 다른 곳으로 (JSX 코드 안에서) 옮기는 것이다.
profile
풀스텍 개발자

0개의 댓글