• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    해결됨

useEffect 실전 활용법2 부분 질문

20.11.05 20:47 작성 23.01.22 08:31 수정 조회수 527

1

끝에 첨부한 코드로 실행하면 작동은 잘 됩니다.

window.addEventListener('click', () => {
    onClickRef.current();
});

문제는 강의에 나오는 코드인 위 코드로 작성하면 removeEventListener를 반환 해주더라도
계속해서 이벤트가 삭제되지 않고 클릭할 때마다 Listener가 계속 생성되면서
Count 가 몇 배로 불어나는 버그가 생깁니다.

addEventListener에서 화살표함수를 사용하여 OnClickRef.current(); 를 호출해주는
이유가 있나요?
물론 removeEventListener없이 화살표 함수로  eventListener 를 실행하여도
의존성 배열부분에 빈배열로 남기면 당연히 작동은 잘 합니다.
그런데 보통 removeEventListener 를 리턴해주는 것이 정석인 것 같은데
화살표함수로 호출한 eventListener는 removeEventListener로 제거가 안되더라구요.
별거 아닌 코드인데 원인을 모르니 답답하네요.

그리고 제가 작성한 코드에서도 이 부분을 ref객체를 useEffect 내에서 
수정하지 않으면 문제가 생기는데 왜 그런지 모르겠네요..
강의에서 설명해주신 바로는 useEffect 내에서 ref객체를 수정하는 이유는 
이후에 있을 concurrent mode로 실행될 때를 대비해 사용하는 것이고,
concurrent mode로 실행하지 않을 시에는 문제가 생기지 않을 것이다라고 하셨는데,
Component 함수에서 직접 수정 시엔 원인 모를 문제로 웹이 다운 되어버립니다. 
느낌상 어떤 부분이 지나치게 렌더링이 되면서 다운 되는 느낌인데 뭔지 모르겠습니다.
왜 이런 현상이 일어나는지 알 수 있을까요?

import { useState } from "react";
import MyComponent from "./components/MyComponent";
 
function App() {
    const [count, setCount] = useState(0);
 
    function onClick() {
        setCount(count + 1);
    }
 
    return (
        <div>
            <MyComponent onClick={onClick}/>
            <div>{count}</div>
        </div>
    );
}
 
export default App;
import { useEffect, useRef } from "react";
 
export default function MyComponent({ onClick }) {
    const onClickRef = useRef();
 
    useEffect(() => {
        onClickRef.current = onClick;
    });
 
    useEffect(() => {
        window.addEventListener('click', onClickRef.current);
        return () => window.removeEventListener('click', onClickRef.current);
    });
 
    return (
        <div>테스트</div>
    );
}

답변 3

·

답변을 작성해보세요.

1

removeEventListener 함수가 실행되는 시점과 ref 객체가 수정되는 시점에 차이가 있기 때문입니다

1. ref 객체 수정을 useEffect 내부에서 하는 경우: removeEventListener 함수가 실행된 후에 ref 객체가 수정됩니다. 따라서 addEventListener 함수에 입력한 함수의 레퍼런스와 같습니다.
2. ref 객체 수정을 컴포넌트 내부에서 하는 경우: removeEventListener 함수가 실행되기 전에 ref 객체가 수정됩니다. 따라서 addEventListener 함수에 입력한 함수의 레퍼런스와 다릅니다.

각 함수의 실행 시점은 아래와 같습니다.
컴포넌트 함수 실행 => 이전에 useEffect 에서 반환된 함수 실행 => useEffect 에 입력된 함수 실행

참고로 아래처럼 작성하면 ref 객체를 언제 수정하든지 항상 해제가 잘 될거에요

useEffect(() => {
  const onWindowClick = onClickRef.current;
  window.addEventListener('click', onWindowClick);
  return () => window.removeEventListener('click', onWindowClick);
});

부담없이 질문해주세요
수강생 분들이 성장해야 저도 보람이 있으니까요^^

1

드로우님의 프로필

드로우

질문자

2020.11.06

답변 감사합니다.
일단 알려주신  코드로는 테스트 해보니 질문 드린 두 부분 모두 정상 작동합니다.
궁금한 점이 아래 코드로는 eventListner를 해제시 레퍼런스가 같아 정상 해제 되는 것으로 보이는데,
useEffect 상이 아닌 Component 함수에서 Ref객체를 수정  시엔 제대로 해제되지 않는 것 같습니다.
이 부분은 왜 그런지 알 수 있을까요?

아래 코드에서 useEffect내에서 Ref객체를 수정 하든 Component 함수에서 수정 하든 레퍼런스는 같으니
해제되어야 함이 정상 아닌가요?
제가 생각 하기로는 Concurrent mode 부분이 실험적인 기능이라
따로 설치해서 import하지 않으면 Concurrent로 작동 하지 않을 것 같은데..
혹시 이 부분이 Concurrent mode와 연관이 있는건가요?

인터넷 강의 보다 책을 먼저 구매해서 따라가다가 강의내 프로젝트가 탐이나서 인강도 지금 듣고있는데
듣다보니 프로젝트가 아니라 질문 & 답변 부분이 이 강의를 구매하는 하나의 메리트란 생각이 드네요.
저도 질문 드릴때마다 어떻게 질문 드리면 의도를 잘 이해하시고 답변해 주실 수 있을까
늘 고민하면서 질문합니다만,  코딩에서는 그 질문도 참 어렵단 생각이 듭니다.

모호한 설명에도 불구하고 질문 의도를 정확하게 파악해주시고
정확한 답변 주셔서 감사합니다.

import { useEffect, useRef } from "react";
 
export default function MyComponent({ onClick }) {
    const onClickRef = useRef();
 
    useEffect(() => {
        onClickRef.current = onClick;
    });
 
    useEffect(() => {
        window.addEventListener('click', onClickRef.current);
        return () => window.removeEventListener('click', onClickRef.current);
    });
 
    return (
        <div>테스트</div>
    );
}

1

안녕하세요
영상의 코드로는 removeEventListener 를 제대로 동작시킬 수 없는 구조입니다.
addEventListener, removeEventListener 두 함수는 입력된 함수의 레퍼런스를 기준으로 등록하고 해제하기 때문인데요
영상의 코드를 기반으로 removeEventListener 를 제대로 동작하게 하려면 아래처럼 변수로 만들어서 활용해야 합니다.
그래야 addEventListener 에 입력한 함수의 레퍼런스가 removeEventListener 쪽과 같아져서 제대로 해제됩니다

export default function MyComponent({ onClick }) {
  const onClickRef = useRef();

  useEffect(() => {
    onClickRef.current = onClick;
  });

  useEffect(() => {
    const onWindowClick = () => {
      sendLog('클릭 되었음');
      onClickRef.current();
    }
    window.addEventListener('click', onWindowClick);
    return () => window.removeEventListener('click', onWindowClick);
  });

  return <div>테스트</div>;
}

******************************************************************************

만약 아래처럼 입력하면 addEventListener, removeEventListener 에 입력되는 함수의 레퍼런스가 달라서 함수가 제대로 해제되지 않습니다.

  useEffect(() => {
    window.addEventListener('click', () => {
      sendLog('클릭 되었음');
      onClickRef.current();
    });
    return () =>
      window.removeEventListener('click', () => {
        sendLog('클릭 되었음');
        onClickRef.current();
      });
  });