Web API의 하나로 Javascript에서 주기적으로 작업을 처리하거나 반복해야 할 상황에 사용할 수 있는 유용한 메소드이다.
setInterval(callbackFunction, delay, [arg1, arg2, ...]);
아래의 코드를 실행한다면 약 1초에 한 번씩 Hello, World! 문구가 출력된다.
setInterval(function () {
console.log('Hello, World!');
}, 1000);
이렇게 setInterval() 은 지정한 시간에 따라 반복적으로 실행되는데 이것을 중지하는 clearInterval() 도 있다.
setInterval() 의 반환 값은 고유한 intervalID로 이 값을 사용하여 인터벌을 중지하거나 취소할 수 있는 것이다.
const printHelloWorld = () => {
console.log("Hello, World!");
};
const intervalId = setInterval(printHelloWorld, 1000);
const stopInterval = (intervalId) => { // 고유한 intervalID를 인수로 받음
clearInterval(intervalId);
};
setTimeout(() => stopInterval(intervalId), 5000); // 5초 후stopInterval()를 호출하여 setInterval() 중지
setTimeout()은 Javascript에서 제공하는 타이머 함수 중 하나로, 지정한 시간이 경과한 후에 한 번만 실행할 함수를 예약할 수 있습니다.
setInterval() 에 대해 알아보았으니 이젠 React에서 사용할 때 어떠한 상황이 발생하는지 살펴보도록 하자
아래의 예시 코드를 테스트 해보면 count의 값이 계속 1이 되는걸 알 수 있다. 이 원인을 이해하려면 Closure와 Event Loop에 대해서 알아보아야 한다.
import React, { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1);
}, 1000);
}, []);
return <div>Count: {count}</div>;
}
export default Counter;
Closure란 간단히 얘기하자면 외부 함수와 안에 있는 내부 함수가 존재하는데 외부 함수의 실행이 종료되었음에도 불구하고 내부 함수가 외부 함수에 접근 할 수 있는 상황을 말한다.
function outer() {
const outerVariable = 'outer 값';
function inner() {
console.log(outerVariable);
}
return inner;
}
const closureFunction = outer();
closureFunction();
위의 예시 코드를 살펴보면 outer() 함수는 inner() 함수를 반환하고, inner() 함수는 outer() 함수의 변수인 outerVariable을 콘솔에 출력한다. outer() 함수의 실행이 완료되어 inner() 함수가 반환된 후에도, 반환된 inner() 함수는 여전히 outerVariable에 접근할 수 있다.
setInterval()의 경우 위에서 말했듯이 Web API이기 때문에 호출되면 바로 실행되지 않고 등록한 delay을 기다렸다가 Callback Queue에 쌓이고, Call Stack이 비워지면 setInterval() 이 Call Stack에 들어간 다음 실행된다.
실행된 setInterval() 은 한 번 호출된 후에 바로 종료하게 된다.
한 번 호출된 후 바로 종료된 setInterval() 로 인하여 setCount 함수가 주기적으로 실행하게 되는데 이때 Closure로 인하여 외부 함수인 setInterval()이 종료되었지만 내부 함수인 setCount는 초기값인 0를 기억하고 있기 때문에 계속해서 1이 되는 것이다. 따라서 아래의 코드처럼 callback를 넘겨주어서 값을 기억하게 만들어야 한다.
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount((prevCount) => prevCount + 1); // 이전의 값을 기억하고 넘겨준다.
}, 1000);
}, []);
return <div>Count: {count}</div>;
}
export default Counter;
React에서는 컴포넌트가 Unmount될 때, 해당 컴포넌트의 상태 및 프로퍼티들이 메모리에서 해제하는데, 이때 useEffect나 useInterval과 같이 비동기 동작을 처리하는 함수가 계속해서 메모리에 남아있을 수 있다. 이러한 상황에서는 메모리 누수(memory leak)가 발생할 수 있기 때문에 비동기 함수를 취소(cancel)하거나 정리(clean up)하는 과정이 필요하다.
아래의 코드는 위에서 말했던 clearInterval() 를 사용하여 메모리 누수를 방지한다.
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount((prevCount) => prevCount + 1); // 이전의 값을 기억하고 넘겨준다.
}, 1000);
return () => {
clearInterval(intervalId); // Unmount시 clearInterval() 이 호출되어 메모리 누수를 방지
};
}, []);
return <div>Count: {count}</div>;
}
export default Counter;
React에서 setInterval() 사용할 때 일어나는 일들이 생각보다 많은 내용을 담고 있어서 놀랐다. 또한 내가 알고 있는 지식이 setInterval() 와 연관되어 있다고 생각하지 않았는데, 이번 기회를 통해 앞으로 새로운 지식에 대해 공부하기 전에 내가 알고 있는 지식과 관련이 있는지 생각해보고 복습하는 습관이 필요하다고 느꼈다.