티스토리 뷰

Goal 

 - 컴포넌트가 unmount된 후 해당 컴포넌트의 state를 업데이트했을 때 발생하는 이슈 공유

 - 해당 이슈 해결 과정 공유

 

리엑트에서 비동기 동작은 정말 흔하다. 서버에 데이터를 요청하거나, setState를 사용하여 state를 수정할 때등 다양한 작업에서 비동기 처리를하게 된다. 

 

비동기 처리가 진행되는 방식과 컴포넌트의 라이프사이클을 잘 알아야한다. 여러 코드가 동작하는 컴포넌트가 unmount 되었는데 비동기 동작으로 인해 해당 컴포넌트의 state를 업데이트해주면 memory leak이 발생할 수 있기 때문이다.

 

개발을 하다가 위와 같은 에러가 발생한다면, 컴포넌트의 생명주기 메서드를 면밀히 살펴볼 필요가 있다. 나도 서비스를 개발하면서 위와 같은 에러를 발생시켰다.

 

현재 개발하는 서비스에 접속하면 다음 사진과 같이 키워드를 팔로우하지 않은 유저에 한해 '관심있는 키워드를 팔로우하세요'라는 툴팁을 보여준다.

서비스의 Main 화면에서 보게되는 Tooltip

 

이 툴팁은 처음 컴포넌트에 진입 후 10초 뒤에 사라진다. 사라지는 동작을 구현 하기위해  setTimeout 함수를 사용했다. 

delay를 통해 툴팁을 보여줄 시간을 입력받고, setTimeout을 통해 해당 시간 뒤에 state를 false로 해줌으로서 툴팁을 사라지게 한다.

  // 툴팁 컴포넌트의 useEffect 메서드
  
  useEffect(() => {
  // props로 받은 delay에 값이 있으면 if문의 코드를 실행한다.
    if (delay && delay > 0) {
    // delay로 초단위를 계산하여 setTimeout을 실행한다.
      const delayTimer = setTimeout(() => {
      // setTimeout이 실행될 때 tooltip을 보여주는 여부를 결정하는 state인 show를 false 해준다.
         setShow(false)
         onClose && onClose()
      }, delay * 1000)
      setTimer(delayTimer)
    }
  }, [])

 

But 여기서 문제가 발생하는데, 메인페이지에 진입한 유저가 바로 다른 페이지로 이동할 경우, 현재 메인페이지 툴팁 컴포넌트는 unmount 되고, setTimeout은 아직 실행되기를 기다리고 있고, setTimeout 함수 안에는 state를 업데이트하는 코드가있기 때문에 불필요하게 메모리를 사용하게 된다. (비동기 동작은 컴포넌트가 unmount되어도 실행된다, 이건 비동기 함수가 어떻게 동작하는지를 알아야하는데 '이벤트루프'라는 키워드를 알아보면 좋다.)

 

Event Loop

 

즉, 컴포넌트가 언마운트 됐을 때 setState함수를 실행할 수 없게 해야한다는 것을 알게 됐다. 문제를 해결하려면, 컴포넌트가 언마운트 되었다는 것을 setState 함수를 실행하기전에 check 해야한다. 따라서 언마운트 시점을 파악해야하며, 그것을 check할 수 있도록 flag를 설정해야한다.

 

다음과 같이 해결했다. 코드를 보면서 짚어보자. useEffect hooks는 컴포넌트가 mount 된 뒤에 실행된다. 따라서 해당 메서드에 mount 여부를 확인할 수 있는 변수를 설정했다. setTimeout안에는 setShow를 mount 상태에서만 할 수 있도록 if문을 주었다. 마지막으로 컴포넌트가 unmount 되기 직전에 실행되는 return 문에 mounted를 false로 설정했다. 

 

이렇게하여 컴포넌트의 mount 여부를 확인할 수 있게 돼었고, 그에 따라 맞추어 setState 동작을 제어할 수 있었다.

  useEffect(() => {
  // 컴포넌트의 mounted 여부를 check하기 위해 변수를 설정한다.
    let mounted = true
    if (delay && delay > 0) {
      const delayTimer = setTimeout(() => {
      // mounted 상태일 때 setShow를 실행할 수 있게 해준다.
        if (mounted) {
          setShow(false)
        }
        onClose && onClose()
      }, delay * 1000)
      setTimer(delayTimer)
    }

// 컴포넌트가 willunmount될 때 실행되는 함수로 이 시점에 mounted 변수를 false로 설정해준다.
    return (): void => {
      mounted = false
    }
  }, [])

 

lifeCycle, 비동기 동작을 깊이있게 이해할 수 있는 경험이었다!

 

참고 자료

https://www.benmvp.com/blog/handling-async-react-component-effects-after-unmount/

 

Handling async React component effects after unmount | Ben Ilegbodu

Four strategies for ensuring that React components don't update state after they've been unmounted

www.benmvp.com

https://stackoverflow.com/questions/58038008/how-to-stop-memory-leak-in-useeffect-hook-react

 

댓글