React - Portals

이소라·2022년 8월 16일
0

React

목록 보기
10/23

Portals

  • Portal
    • 부모 컴포넌트의 DOM 계층 구조 바깥에 존재하는 DOM 요소의 자식을 렌더링하는 방법을 제공함
    • portal이 부모 DOM 요소 바깥에서 렌더링되더라도, 애플리케이션 내의 React 컴포넌트와 비슷하게 동작함 (props와 context API에 접근 가능함)
      • 이유 : DOM 트리에서의 위치와 상관없이 poral은 React 트리 내에 존재하기 때문임
React.createPortal(child, container)
  • React.createPortal를 사용하여 portal을 생성함
    • 첫 번째 인자(child) : 렌더링할 수 있는 React의 자식임(요소, 문자열, fagment 등)
    • 두 번째 인자(container) : portal이 들어갈 DOM 노드



Portal 사용법

  • 자식 컴포넌트가 부모 컴포넌트의 container에서 시각적으로 벗어나야 할 경우 주로 사용됨

    • 부모 컴포넌트 내에서 modal을 사용할 경우, modal이 들어있는 컴포넌트로부터 모달의 폭, 높이를 상속받으므로 modal이 시각적으로 잘릴 가능성이 있음
    • 이러한 문제를 피하기 위해 modal은 overflow: hiddenz-index 같은 CSS property가 필요함
    • portal을 사용하면 modal이 부모 컴포넌트 밖에서 렌더링되므로, 부모 컴포넌트로부터 폭과 높이를 상속받지 않음
  • Potal's use cases

    • Modal dialog boxes
    • Tooltips
    • Hovercards
    • Loaders
  • portal을 사용할 때, 키보드 focus 관리가 매우 중요함

    • 예) Modal이 닫힌 후, Modal을 열었던 버튼으로 키보드 focus를 다시 맞춰주어야 함
    • React에서는 DOM 요소에 ref를 사용하여 focus를 지정함

Portal in Class Component

  • 일반적으로 컴포넌트의 render 메서도에서 반환된 요소가 가장 가까운 부모 컴포넌트의 자식으로서 DOM에 마운트됨
  • portal을 사용해서 DOM의 다른 위치에 자식을 삽입할 수 있음
class Modal extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      domNode
    );
  }
}

Portal in Funtional Component

const Modal = ({ message, isOpen, onClose, children }) => {
  if (!isOpen) return null;
  return ReactDOM.createPortal(
    <div className="modal">
      <span className="message">{message}</span>
      <button onClick={onClose}>Close</button>
    </div>,
    domNode);
}



Portal을 통한 이벤트 버블링

  • portal 내부에서 발생한 이벤트는 React 트리에서의 상위 컴포넌트로 전파됨
    • DOM 트리에서 상위에 존재하지 않더라도 React 트리에서 상위에 존재한다면 이벤트가 전파됨
<html>
  <body>
    <div id="app-root"></div>
    <div id="modal-root"></div>
  </body>
</html>
  • '#app-root' 안에 있는 Parent 컴포넌트는 형제 노드인 '#modal-root' 안의 컴포넌트에서 전파된 이벤트가 포착되지 않았을 경우 그 이벤트를 포착할 수 있음
// appRoot와 modalRoot은 DOM에서 형제 관계임
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

class Modal extends React.Componet {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }
  
  componentDidMount() {
    // Portal 요소는 Modal의 자식이 마운트된 후 DOM 트리에 삽입됨
    // 자식은 어디에도 연결되지 않은 DOM 노드로 마운트 됨
    // 자식 컴포넌트가 마운트될 때 그것을 즉시 DOM 트리에 연결해야 한다면 (DOM  노드 계산, 자식 노트에서 'autoFocus' 사용),
    // Modal에 state를 추가하고 Modal이 DOM 트리에 삽입되어 있을 때만 자식을 렌더링하기
    modalRoot.appendChild(this.el);
  }
  
  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }
  
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { clicks: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    // Child 컴포넌트 내에 있는 버튼이 클릭했을 때, 그 버튼이 DOM 상 직계 자식이 아니더라도 이벤트 버블링에 의해 이벤트가 전파되어 Parent의 state를 갱신함
    this.setState(state => ({
      clicks: state.clicks + 1
    }));
  }
  
  render() {
    return (
      <div onClick={this.handleClick}>
        <p>Number of clicks: {this.state.clicks}</p>
      </div>
      <Modal>
        <Child />
      </Modal>
    )
  }
}
  • portal에서 버블링된 이벤트를 부모 컴포넌트에서 포착한다는 것은 portal에 의존하지 않는 유연한 추상화 개발이 가능하다는 것을 나타냄
    • 예) Modal 컴포넌트를 렌더링할 때, 부모 컴포넌트는 Modal 컴포넌트의 portal 사용 여부와 관계 없이 Modal 컴포넌트의 이벤트를 포착할 수 있음



Portal을 사용할 때 고려할 점

  • 이벤트 버블링이 일어남

    • portal에서 발생한 이벤트는 DOM에서의 portal 노드의 위치와 관계 없이 React 트리의 상위로 전파됨
  • React가 portal 노드와 그 생명주기를 제어함

    • portal을 통해 자식 요소들이 렌더링될 때, React가 여전히 그 요소들의 생명주기를 제어함
  • portal은 DOM 구조에만 영향을 미침

    • portal은 HTML DOM 구조에만 영향을 미치고, React 컴포넌트 트리에는 영향을 미치지 않음
  • HTML 마운트 지점을 미리 정의해야 함

    • portal을 사용할 때, portal 컴포넌트의 마운트 지점인 HTML DOM 요소를 정의해야 함



참고

0개의 댓글