5주차 세오스 과제를 진행하다가
useEffect(() => {
setTimeout(() => {
~~~
},5000)
},[])
와 같은 코드를 작성했는데, 지피티선생님께서 useEffect
안의setTimeout
를 선언한 경우 return
에서 clearTimeout
을 통해 지워주는것이 좋다고 하셨다.
그때는 그냥 막연히 메모리 정리와 최적화의 개념이구나~하고 넘어갔었는데,
프론트 팀메이트가 왜?라는 의문을 제기하였고, 다시 생각해보아도
정확히 어떤 이유에서 메모리 정리가 되고, 최적화가 되는지 모르겠어서
테스트와 리서치를 조금 해보았다.
기본적인 코드를 아래와 같이 작성했다.
//App.js
import { useEffect, useState } from "react";
function App() {
const [countVal, setCountVal] = useState(1);
useEffect(() => {
setTimeout(() => {
setCountVal(countVal * 10);
}, 3000);
}, [countVal]);
const Counter = () => {
return <h2>count : {countVal}</h2>;
};
return (
<div>
<h1>Testing Timeout</h1>
<Counter />
<button onClick={() => setCountVal(countVal + 1)}>Increment</button>
</div>
);
}
export default App;
3초에 한번씩 timeout
이 실행되어 count
값이 10배로 증가하고,
Increment
버튼을 클릭하면 현재 count
값에 1을 더하는 코드이다.
count
값이 100으로 변한 후에 increment
를 1초에 1번씩 두번 누르면
100(0초) -> 101으로 증가(1초) -> 102로 증가(2초) -> timeout실행 : 1020으로 증가(3초) -> (4초) -> (5초) -> 10200으로 증가(6초) ...
와 같이 진행될 것으로 예상했다.
예상과 같이 진행했을 때의 결과이다.
100(0초) -> 101으로 증가(1초) -> 102로 증가(2초) -> timeout실행: 1000으로 증가(3초) -> 1010으로 증가(4초) -> 1020으로 증가(5초) -> 10000으로 증가(6초)...
어찌보면 당연한 결과였지만, 놓친 부분이였다.
countVal
이 dependency
에 있기 때문에 countVal
의 변화가 될 때마다 새로운 setTimeout
함수가 생성되고있었다.
다만, 예상하지 전혀 예상하지 못했던 부분은
102로 증가(2초) 후에 가장 처음의 의도한 timeout
(10배 곱하는 함수)이 실행 될 때
1020으로의 증가가 아닌, 1000으로의 증가가 되는 점이였다.
useEffect
는 dependency
에 있는 값들을 지켜보고 있다가, 변화가 생기면 useEffect
내부의 코드를 실행시킨다.
이때, 변화를 감지하면 새로운 값(closure
)을 capture하여 내부의 코드들에 전달하게 된다.
내부의 코드들이 실제로 실행되는 시간은 setTimeout
으로 인하여 3초 후이여서, 전달된 100의 값이 3초 후 1000으로 변하는 것이다.
useEffect(() => {
const timeout1 = setTimeout(() => {
setCountVal(countVal * 10);
}, 3000);
return () => clearTimeout(timeout1);
},[countVal]);
제시할 수 있는 가장 간단하고, 에러가 적을 해결책은 setTimeout
함수를 변수에 저장하고, 해당 변수를 useEffect
의 return
문에 clearTimeout
을 적어줌으로서 callStack에서 제거해주는 방법이다.
100(0초) -> 101(1초) -> 102(2초) -> ...-> 1020(5초) -> ...
의 단계로 진행되는것을 확인할 수 있다.
직접 코드로 구현은 어려워서 상황만 정리하자면, useEffect
의 return
에서 clearTimeout
을 통해 해제해주지 않는다면, 다음과 같은 오류가 발생할 수도 있다.
timeout
함수가 실행됨timeout
이 종료되고 timeout
으로 실행하는 함수를 호출함state
를 변경하려 하기 때문에 react가 error을 throw.useEffect
안에서 setTimeout
으로 timeout함수를 생성한 경우, return
에서 clearTimeout
을 통해 timeout을 clear해주는것이
1. potential memory leak(잠점적인 메모리 누수)
2. executing state updates on an unmounted component(언마운트된 컴포넌트의 상태 업데이트 실행)
들을 방지할 수 있다고 할 수 있겠다.