[21/06/18] 오늘의 개발

15156 단어 TILTIL

모달

다음과 같은 모달을 구현하기 위한 ModalWrapper 컴포넌트를 만들 계획.



  • 아이디어 : 모달래퍼는 감싼 컴포넌트, 즉 자식 컴포넌트들을 띄워주는 역할만을 수행한다.
function Modal({ children, setIsModalShow }) {
  const modalRef = useRef();
  const onClickModal = (e) => {
    if (modalRef.current.className === e.target.className) {
      // 모달 콘텐츠가아닌 모달 바깥누르는 경우
      setIsModalShow(false);
    }
  };
  return (
    <Wrapper ref={modalRef} onClick={onClickModal}>
      {children}
    </Wrapper>
  );
}
  • 모달 바깥을 누르면 모달은 안보이게된다. onClickModal

  • children 태그는 모달래퍼로 감싼 자식 컴포넌트

다음은 모달 래퍼의 최상단 컴포넌트이다.

const Wrapper = styled.div`
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;

  background-color: rgba(0, 0, 0, 0.5);
`;

positionfixed로 두고, top:0 / left:0를 두어 모달을 구현한다.

useDetectOutsideClick 훅

다음의 코드를 분석해보려고 한다.

const useDetectOutsideClick: any = (el: React.RefObject<HTMLDivElement>, initialState: boolean) => {
  const [isActive, setIsActive] = useState(initialState);
  useEffect(() => {
    const pageClickEvent = (e: Event) => {
      if (el.current && !el.current.contains(e.target as Node)) {
        setIsActive(!isActive);
      }
    };

    if (isActive) {
      window.addEventListener('click', pageClickEvent);
    }

    return () => {
      window.removeEventListener('click', pageClickEvent);
    };
  }, [isActive, el]);

  return [isActive, setIsActive];
};
  • 인자 : el( div ref object ), initialState ( initial value of isModalShow property )

우선 isModalShow 모달 보여주는 것을 제어할 state를 만든다.


  const [isActive, setIsActive] = useState(initialState);

다음으로 pageClickEvent 리스너 함수를 만들어준다.

const pageClickEvent = (e: Event) => {
      if (el.current && !el.current.contains(e.target as Node)) {
        // ref 객체의 current, 즉 ref에 값이 잡히는 경우 (렌더 메서드 안에서 ref가 엘리먼트에게 전달되었을 때)
        
        // ref 객체의 current요소가 e.target(클릭한 요소)를 포함하고 있지 않다면, 모달 내부의 컴포넌트가 아닌 외부를 클릭한 경우
        
        // setIsActive(!isActive) isActive를 반대로 
        setIsActive(!isActive);
      }
};

render 메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 됩니다.

이렇게 이벤트 리스너를 만들었으면

모달이 켜져있는 경우 이 이벤트 리스너를 달아주고, 모달 컴포넌트가 사라지면 이벤트를 제거한다. (이거 안해주면 이벤트가 누적되어 부착됨)

 useEffect(() => {
    const pageClickEvent = (e: Event) => {
      if (el.current && !el.current.contains(e.target as Node)) {
        setIsActive(!isActive);
      }
    };

    if (isActive) {
      window.addEventListener('click', pageClickEvent);
    }

    return () => {
      window.removeEventListener('click', pageClickEvent);
    };
  }, [isActive, el]);

isActive를 훅을 사용한 컴포넌트에서 받아 모달을 제어하는 데 사용하면 된다.

useEffect vs useLayoutEffect

대부분의 effect는 동기적으로 실행될 필요가 없습니다. 흔하지는 않지만 (레이아웃의 측정과 같은) 동기적 실행이 필요한 경우에는 useEffect와 동일한 API를 사용하는 useLayoutEffect라는 별도의 Hook이 존재합니다.

  • useEffect

리액트가 effect를 정리(clean-up)하는 시점은 정확히 언제일까요? 리액트는 컴포넌트가 마운트 해제되는 때에 정리(clean-up)를 실행합니다.

Effect를 이용하여 서로 관련이 없는 로직들을 갈라놓을 수 있습니다. 서로 관련 없느 로직은 다른 UseEffect로 분리하도록한다.

의존 배열을 []로 두면, 첫 렌더링 직후 실행

컴포넌트가 처음 나타날때에만 useEffect 에 등록한 함수가 호출됩니다.

Reference

좋은 웹페이지 즐겨찾기