본문 바로가기
개발/React

useEffect의 cleanUp 함수에 대한 착각 : unmount시 발생하는 것이 아니다!

by 안뇽! 2022. 8. 12.
반응형

useEffect의 cleanUp 함수에 대한 착각 : unmount시 발생하는 것이 아니다!

들어가기에 앞서 결론부터 말하자면 공식문서에 나와있다

https://ko.reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update

 

실제 실험을 통해 페이지 이동(unmount)이 안되어도 setInterval을 정리하여 버그를 차단함을 알 수 있었다.

 

1. clean-up 없을 때

import { useEffect, useState } from "react";

const initialCount = 0;
function FrequentlyChangableDependencyInEffect() {
  const [count, setCount] = useState<number>(initialCount);

  useEffect(() => {
    setInterval(() => {
          setCount(count); 
        }, 1000);
    
      }, [count]);
      
    

  return (
    <>
      <h1>{count}</h1>
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
    </>
  );
}

export default FrequentlyChangableDependencyInEffect;

useEffect에 setInterval넣고 clean-up 작성 안했을 때

state가 꼬여버린다.

 

2. clean-up 있을 때

(+ 공식문서 참고하여 functional Update 로 변형함 )

import { useEffect, useState } from "react";

const initialCount = 0;
function FrequentlyChangableDependencyInEffect() {
  const [count, setCount] = useState<number>(initialCount);

  useEffect(() => {
    const id = setInterval(() => {
      setCount((c) => c + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [count]);

  return (
    <>
      <h1>{count}</h1>
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
    </>
  );
}

export default FrequentlyChangableDependencyInEffect;

useEffect안에 setInterval넣고 clean-up추가

 

이는 페이지 전환이 되지 않아도 매 업데이트시마다 clean-up이 실행되어 setInterval을 정리해주는 모습이다.

 

이제 설명으로 들어가자!


 

Clean-up 함수란?

useEffect의 clean-up함수는 쉽게 생각하면 뒷정리를 해주는 함수이다.

 

공식문서에 따르면 

 

다음 3가지 경우는 clean-up함수가 필요 없는 경우이다.

  • DOM 수동조작
  • 네트워크 요청
  • 로깅

실행 이후 신경쓸 것이 없기 때문

 

clean-up함수가 필요한 경우는 다음과 같다.

  • setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
  • 라이브러리 인스턴스 제거

페이지 전환시에 남아있으면 안되는 기능들을 정리할 때 쓰인다. (예시 : setTimeout때문에 메모리 누수가 생기는 상황)

useEffect(()=>{
	let mounted = true;
    
    setTimeout(()=> {
    	if(mounted){
        	return NextBtn();
        }
    },5000)
        
    return () => {mounted = false;};
});

// 페이지 전환시 setTimeout이 실행되지 않도록 함

 

그럼 clean-up함수는 언마운트시 1회 실행되는 것인가?

 

공식문서를 보면 그렇지 않다.

 

https://ko.reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update
https://www.zerocho.com/category/React/post/579b5ec26958781500ed9955

우선 결론부터 말하면, class의 생명주기로 굳이 치환했을 때 componentWillUnmount가 아니라 componentDidUpdate였다.

(클래스형생명주기를 쉽게 나타낸 글)

 

(블로그들에서 clean-up함수가 unmount될 때 1번 실행된다는 글들이 있는데 다 잘못알고 하는 말인 듯 하다. 물론 나도 그렇게 썼고)

 

컴포넌트가 화면에 표시되어 있는 동안 friend prop이 변한다면 컴포넌트는 다른 friend의 상태에 영향이 갈 수 있다.

또 마운트 해제가 일어나는 동안 구독 해지 호출이 다른 friend ID를 사용하여 메모리 누수나 충돌이 발생할 수 있다.

 

클래스형 리액트에서는 이런 경우들을 다루기 위해 componentDidUpdate를 사용한다.

클래스형 예시

props 변화 처리가 완료되었을 때 이전 friend를 unsubscribe시키고 다음friend를 subscribe 시키는 것이다.

 

이를 함수형 컴포넌트에서 표시하면 다음과 같다!

함수형 예시

 

위와 같은 경우는 언마운트시 실행된 것이 아니라, friend가 바뀌었을때

이전 friend를 unsubscribe시키고 현재 friend를 subscribe시킨 것이다.

 

위 함수형 예시 코드를 실행순서대로 풀면 subscribe와 unsubscribe를 반복하는 다음 코드가 된다.

useEffect 실행 내용을 순서대로 풀어낸것

 

다시한번 말하지만, unsubscribe시키는 clean-up함수는 unmount시 실행된 것이 아니라, friend가 바뀌었을 때 실행되는 것이다.

 

매 랜더링마다 다음의 effect를 적용하기 전에 이전의  effect를 정리(clean-up)하는 것이다.(의존성 배열이 있다면 의존성 배열의 값이 변할때마다)

 


참고
리액트 공식문서

클래스형 리액트 생명주기 이해하기 쉬운 그림

 

반응형