React

[React] useEffect와 cleanup함수

chaego 2024. 3. 10. 18:11

useEffect의 cleanup함수에 대해 알아보자 ❕

 

지난 포스터와 같이 useEffect에서 cleanup 함수를 사용해야 할 때가 있다. cleanup 함수가 필요한 경우와 동작 과정이 어떻게 되는지 정리하려고 한다.

 

 

 

1. useEffect 란?


useEffect는 컴포넌트가 렌더링 될 때마다 특정 작업(Side effect)을 실행할 수 있도록 하는 리액트 Hook이다. 컴포넌트가 mount, unmount, update됐을 때 특정 작업을 실행시킬 수 있다. 

 

 

기본 형태는 useEffect(setup, dependencies?)로 사용되며, dependencies는 생략할 수 있다. dependencies에 빈 배열([ ])을 입력한 경우 컴포넌트가 처음 mount되었을 때 한번 호출되고, dependencies에 특정 값을 넣은 경우 이 값들이 변경될 때마다 호출된다.

 

 

다음과 같이 useEffect를 작성했다면, 컴포넌트가 처음 mount되었을 때 "hello"가 출력되고 상태값 dep가 변경될 때마다 "안녕"이 출력된다.

useEffect(() => {
      console.log("hello")
}, [])


useEffect(() => {
    console.log("안녕")
}, [dep])

 

 

 

2. cleanup함수란?


useEffect에서는 함수를 반환할 수 있는데 이를 cleanup 함수라고 부른다. cleanup 함수를 사용하면 컴포넌트가 unmount될 때 or 특정 값이 업데이트되기 직전에 실행할 작업을 지정할 수 있다.

 

 

즉, cleanup 함수는 다음과 같이 상황에서 실행된다.

  1. dependencies가 빈 배열([ ])인 경우, 컴포넌트가 unmount될 때
  2. dependencies에 특정 값을 설정한 경우, 해당 값들이 업데이트되기 직전

 

(2) 의 예로, dependencies에 상태값 count를 넣고 cleanup 함수가 언제 실행되는지 확인해봤다.

'더하자'라는 버튼을 클릭했을 때, count가 1 증가하도록 했다.

import { useEffect, useState } from "react"

export function Test() {
   const [count, setCount] = useState(0);

   useEffect(() => {
       console.log(count);

       return () => console.log("cleanup : " + count);        // cleanup 함수
   }, [count])

   const handleActionClick = () => {
       setCount(prev => prev + 1)
   }

   return (
       <>
           <button onClick={() => handleActionClick()}>
               더하자
           </button>
       </>
   )
}


순서를 보면, count가 변경되기 전에 cleanup함수가 실행되어 `cleanup : count`가 출력되고, 그 후 변경된 `count+1`가 출력된다.

따라서 특정 값이 변경되기 전에 어떠한 작업을 수행하고 싶다면 cleanup 함수로 등록해 사용할 수 있다.

 

 

 

3. useEffect에서 cleanup 함수가 필요한 경우


React 공식문서에는 `setInterval()`, `window.addEventListener()` 등과 같이 React에 의해 제어되지 않는 작업에서 cleanup 함수를 사용하라고 한다.

 

 

예제

'버튼 활성화'을 클릭해 '더하기' 버튼을 활성화시키고, '더하기' 버튼을 클릭해 count를 증가시키려고 한다.

(여기서 '더하기'의 클릭 이벤트는 addEventListener()로 등록해 사용한다)

 

 

import { useEffect, useState } from "react"

export function Test() {
    const [isActive, setIsActive] = useState(false);   // 버튼 활성화 여부
    const [count, setCount] = useState(0);

    useEffect(() => {
        if(!isActive) {
            return
        }

        document.getElementById("add-button").addEventListener('mousedown', handleActionClick);
    }, [isActive])

    const handleActionClick = () => {
        setCount(prev => prev + 1)
    }

    const handleChangeButtonActive = () => {
        setIsActive(!isActive);
    }

    return (
        <>
            <div>
                <button onClick={() => handleChangeButtonActive()}>
                    {isActive ?
                        <span>버튼 비활성화</span>
                        :
                        <span>버튼 활성화</span>
                    }
                </button>
            </div>
            <div>
                <button id="add-button" disabled={!isActive}>
                    더하기
                </button>
            </div>
            <div>{count}</div>
        </>
    )
}

하지만 의도한 대로 동작하지 않는다. 일정하게 1씩 증가되지 않고, 버튼이 활성화 될 때(n)마다 count 값이 n+1씩 증가된다. 이는 isActive가 변경 될 때마다 useEffect의 document.getElementById("add-button").addEventListener('mousedown', handleActionClick)가 실행되고 이벤트리스너가 여러번 등록되어 발생하는 문제이다.

 

 

이 문제를 useEffect의 cleanup함수로 해결할 수 있다.

import { useEffect, useState } from "react"

export function Test() {
    const [isActive, setIsActive] = useState(false);
    const [count, setCount] = useState(0);

    useEffect(() => {
        if(!isActive) {
            return
        }

        document.getElementById("add-button").addEventListener('mousedown', handleActionClick);

        // cleanup : 이벤트리스너 제거
        return () => document.getElementById("add-button").removeEventListener('mousedown', handleActionClick);
    }, [isActive])

    const handleActionClick = () => {
        setCount(prev => prev + 1)
    }

    const handleChangeButtonActive = () => {
        setIsActive(!isActive);
    }

    return (
        <>
            <div>
                <button onClick={() => handleChangeButtonActive()}>
                    {isActive ?
                        <span>버튼 비활성화</span>
                        :
                        <span>버튼 활성화</span>
                    }
                </button>
            </div>
            <div>
                <button id="add-button" disabled={!isActive}>
                    더하기
                </button>
            </div>
            <div>{count}</div>
        </>
    )
}

 

이제 버튼을 여러번 활성화시켜도 일정하게 1씩 증가하게 된다.




 

참고자료 😃
https://legacy.reactjs.org/docs/hooks-effect.html
https://react.dev/reference/react/useEffect#useeffect