ABOUT ME

jacey.park.dev@gmail.com \n 5년차 프론트엔드 개발자

Today
-
Yesterday
-
Total
-
  • [JS] React.js Portal 알아보기
    Dev/JS 2018. 6. 3. 16:34

    Portal


    Portal은 상위 컴포넌트의 DOM 계층을 구조 외부에 있는 DOM의 하위 항목으로 렌더링 하는 좋은 방법을 제공합니다.



    ReactDOM.createPortal(child, container)




    createPortal의 첫번째 인자인 child 는 요소, 문자열 또는 Fragment 와 같은 렌더링이 가능한 React의 하위 요소 입니다.

    두번째 인수 container은 은 DOM 요소를 말합니다.




    Usage


    일반적으로, 컴포넌트의 render 메소드에서 요소를 반환하면 가장 가까운 부모 노드의 자식으로 DOM에 마운트됩니다. 



    render() {
    // React는 새로운 Div를 마운트 하고 자식요소에 렌더링 합니다.
    return (
    <div>
    {this.props.children}
    </div>
    );
    }



    하지만, 때로는 DOM의 다른 위치에 자식 요소를 넣는 것이 유용합니다.



    render() {
    // React는 새로운 div를 만들지 않습니다. 자식을 domNode에 렌더링 합니다.
    // DOM의 위치에 관계 없이 domNode는 정상적인 DOM node 입니다.
    return ReactDOM.createPortal(
    this.props.children,
    domNode
    );
    }



    포털은 일반적으로 부모 컴포넌트에 overflow: hidden 또는 z-index 스타일이 있는 경우이지만, 컨테이너에서 시각적으로 점점 보이지 않아야 할 때 사용합니다. 예를들어 모달, 다이얼로그, 툴팁 등이 있겠죠?


    참고 : 포털을 사용하여 작업을 할 때에는 적절한 접근성 지침을 따라야 합니다.



     Event Bubbling Through Portals


    portal 은 DOM 트리에 어느곳에서나 있을 수 있지만, 일반적인 React의 하위 컴포넌트들처럼 작동합니다.

    컨텍스트와 같은 기능은 DOM 트리의 위치에 관계없이 React 트리에 있으므로 포털이라고 다르게 작동하지 않고,

    다른 컴포넌트들과 동일하게 작동합니다.


    이때 이벤트 버블링도 포함됩니다. portal 내부에서 시작된 이벤트는 DOM 트리의 조상이 아니더라도 포함되는

    React 트리의 조상에게 전파됩니다. 다음과 같이 말이죠.



    <html>
    <body>
    <div id="app-root"></div>
    <div id="modal-root"></div>
    </body>
    </html>



    #app-root에 있는 조상 컴포넌트는 #modal-root의 조상 요소의 이벤트 버블링을 캐치할 수 있습니다.



    // 이 두개의 컨테이너는 DOM 에서의 조상입니다.
    const appRoot = document.getElementById('app-root');
    const modalRoot = document.getElementById('modal-root');


    class Modal extends React.Component {
    constructor(props) {
    super(props);
    this.el = document.createElement('div');
    }


    componentDidMount() {
    // portal 요소는 이후 DOM 트리에 삽입됩니다.
    // Modal의 자식요소가 mounted 되면,
    // 자식 요소는 분리된 DOM 노드로 mounted 될 것입니다. 만약 자식
    // 요소가 DOM 트리에 붙어야 한다면 그 즉시
    // mounted 되며, 예를 들어 DOM node, 혹은 감소 와 같이 'autoFocus'
    // 를 사용해야 한다면, state를 모달에 추가하여 모달이 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() {
    // 이곳은 자식요소 안에있는 버튼이 클릭되면 사라질 것 입니다.
    // 또한, 부모의 state를 업데이트 하거나, 버튼이
    // DOM안에 다이렉트 되지 못할 때도 포함입니다.
    this.setState(prevState => ({
    clicks: prevState.clicks + 1
    }));
    }


    render() {
    return (
    <div onClick={this.handleClick}>
    <p>Number of clicks: {this.state.clicks}</p>
    <p>
    Open up the browser DevTools
    to observe that the button
    is not a child of the div
    with the onClick handler.
    </p>
    <Modal>
    <Child />
    </Modal>
    </div>
    );
    }
    }


    function Child() {
    // 이 클릭 이벤트는 부모 요소로 버블링 됩니다.
    // 왜냐면 이곳에는 'onClick' 속성이 없기 때문이죠
    return (
    <div className="modal">
    <button>Click</button>
    </div>
    );
    }


    ReactDOM.render(<Parent />, appRoot);




    상위 컴포넌트의 Portal에서 이벤트를 받으면, portal에 의존하지 않고, 좀 더 유연한 추상화 작업이 가능합니다.


    예를 들어, <Modal /> 컴포넌트를 렌더링 하면 부모 요소는 Portal 를 사용하여 구현되었는지 여부에 관계없이

    이벤트를 캡쳐할 수 있습니다.


    감사합니다.

    댓글

Designed by Tistory.