인프런 커뮤니티 질문&답변

working.zima님의 프로필 이미지
working.zima

작성한 질문수

[React / VanillaJS] UI 요소 직접 만들기 Part 2

모달 (2/4) createPortal 활용

createPortal 활용해서 modal 만들 때 활용한 MutationObserver 코드 관련 질문

해결된 질문

작성

·

167

·

수정됨

0

import { useEffect, useRef } from "react"

const mutationObserverOption: MutationObserverInit = {
  childList: true,
  subtree: true
}

const ModalRoot = () => {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let observer: MutationObserver
    if (ref.current) {
      observer = new MutationObserver(() => {
        const size = ref.current?.childNodes.length || 0
        document.body.classList.toggle('no-scroll', size > 0)
      })
      observer.observe(ref.current, mutationObserverOption)
    }

    return () => {
      observer.disconnect()
    }
  }, [])

  return (<div id="modalRoot" ref={ref}/>)
}

export default ModalRoot;

수업시간에 구현 되었던 코드가 어떤 순서로 동작하는지 콘솔로 확인해봤습니다.
제가 확인해 봤을 땐 처음 페이지가 렌더링 될 때

1. ModalRoot 컴포넌트가 실행
2. modalRoot div가 생성
3. useEffect가 실행
4. useEffect의 Clean Up 작동

까지는 예상대로 진행되었습니다.
하지만 이후에 이해가 안되는 부분이 있습니다.

질문 1) 이후 useEffect가 다시 실행되는데, 의존성도 없는데 어떻게 다시 실행되는지 궁금합니다.

질문 2) 영상에서 모달 버튼을 누르면 useEffect의 조건문에서 size를 콘솔로 확인하셨는데, ModalRoot 컴포넌트가 재실행되지 않고 어떻게 size를 확인할 수 있는지 궁금합니다.

답변 3

1

정재남님의 프로필 이미지
정재남
지식공유자

답변이 늦은 것 같진 않은데, 그 얼마 안되는 사이에 AI가 답변을 해버렸군요.

  • useEffect에 의존성이 비어 있으면 상태 변화 등으로 업데이트가 발생하더라도 재실행되지 않습니다. 이 부분은 AI가 잘못된 정보를 전달한 것 같습니다.

  • 그럼에도 재실행된 것은, 질문자께서 추가 작성하신 것처럼 StrictMode 때문입니다.
    https://ko.react.dev/reference/react/StrictMode#fixing-bugs-found-by-re-running-effects-in-development

  • MutationObserver는 감시를 시작(observe)한 순간부터 해제(unobserve 또는 disconnect)되기 전까지 계속 등록한 대상의 변화를 감시하며, 변경사항이 감지될 때마다 초기 인스턴스 생성시에 전달한 함수를 실행합니다(그래서 감시자, observer라고 합니다). 클로저와는 무관하고, 이벤트 핸들러를 등록(adEventListener)한 것과 비슷하다고 이해하시면 됩니다.

working.zima님의 프로필 이미지
working.zima
질문자

선생님 modal 파트에서 질문이 하나 더 있습니다.
제가 아는 지식으로는 ref props로 넘기려면 forwardRef를 사용해야하는데, 수업에서는 forwardRef 없이 ref 를 넘기는 것으로 보입니다.
어떤 경우에 forwardRef 없이 ref 를 넘길 수 있는것일까요?
공식문서를 찾아봐도 ref 전달하기에는 해당 내용이 없는 것 같아서 질문드립니다.

정재남님의 프로필 이미지
정재남
지식공유자

forwardRef는 부모 컴포넌트가 자식 컴포넌트의 노드에 접근하고자 할 때 씁니다. 부모에서 ref를 생성하고, 자식의 노드에 연결할 때 쓰는 것입니다.

https://ko.react.dev/reference/react/forwardRef#exposing-a-dom-node-to-the-parent-component

반면 부모 컴포넌트의 노드를 자식 컴포넌트에 전달할 때는 그냥 props로 전달하면 됩니다. 부모에서 ref를 생성하고, 부모의 노드에 연결한 다음, 연결된 결과를 자식에게 전달하는 것입니다. 이 경우 ref는 부모 컴포넌트에서 생성 및 연결까지 완료되었으므로 forward해줄 이유가 없습니다.

 

1

안녕하세요, 인프런 AI 인턴입니다.

working.zima 님의 질문과 유사한 토론이 이전에 있었습니다. 질문 1에 대해, useEffect가 다시 실행되는 부분과 관련하여, React의 useEffect Hook은 컴포넌트가 마운트될 때(처음 렌더링될 때) 뿐만 아니라 컴포넌트가 업데이트될 때도 호출됩니다. 여기서 의존성 배열이 비어있다 할지라도, 컴포넌트 내부에서 상태 변화나 재렌더링으로 인해 업데이트가 발생할 경우, useEffect 내부의 Clean Up 함수 실행 후 다시 useEffect가 실행됩니다.

질문 2와 관련, MutationObserver는 DOM 변화를 비동기적으로 감지합니다. 따라서, ModalRoot 컴포넌트 자체가 재실행될 필요 없이, MutationObserverref에 바인딩된 DOM 요소 내의 변화(자식 노드의 추가 및 제거 등)를 감지하고 있기 때문에, 해당 변화가 발생할 때마다 콜백 함수가 실행되어 size 변경을 확인할 수 있습니다.

여기서 관련된 코드 예시와 설명이 있는 기존 답변을 찾아보니, 테일윈드로 포탈로 모달작성시 뒤에 클릭이 안됩니다와 밀접하게 관련되어 있지만, 직접적으로 working.zima 님의 질문을 해결하는 내용이 아니므로 다소 차이가 있음을 알립니다. 이 예시는 ModalRootProvider 컴포넌트 내에서 MutationObserver를 사용한 구체적 방법이 설명되어 있습니다. 여기서는 모달 루트 컨테이너의 자식 요소 변경을 감지하고, 해당 감지에 따른 동작(스크롤 비활성화 등)을 하는 방식을 취하고 있습니다.

또한, 실제로 useEffect가 언제 왜 실행되는지, 그리고 MutationObserver가 동작하는 원리에 대해서 더 깊게 이해하고 싶다면, React 공식 문서에서 Hooks API Reference와 MDN에서 MutationObserver를 참고하는 것이 좋습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

0

working.zima님의 프로필 이미지
working.zima
질문자

먼저 답변 감사합니다.

1번 질문은 혼자 여러 확인을 해본 결과 strictMode로 인해 두 번 실행되는 것으로 보입니다.

2번 질문은 클로저 또는 비동기일 수 있겠다라고 예상해서 해소된거 같습니다.

working.zima님의 프로필 이미지
working.zima

작성한 질문수

질문하기