[참고] setInterval() - Web APIs | MDN
[참고 #1] Dan Abramov, Making setInterval Declarative with React Hooks
[참고 #2] 번역 / 리액트 훅스 컴포넌트에서 setInterval 사용 시의 문제점
[참고 #3] React에서 setInterval 사용하기
clearInterval()
함수를 통해 제거할 수 있음setInterval()
함수의 Parametersfunc
: delay 밀리초마다 실행될 콜백 함수delay
(optional): 함수를 실행시킬 간격을 나타내는 밀리초로 옵셔널 값으로 지정되지 않을 경우 기본값은 0arg0, ..., argN
(optional): 옵셔널 값으로 콜백함수의 인자로 넘어가는 인자들 // 3초(=3000밀리초) 간격으로 callbackFunc을 실행
// callbackFunc에 첫 번째 인자로 "first", 두 번째 인자로 "second" 값을 넘겨줌
const intervalID = setInterval(callbackFunc, 3000, "first", "second");
function callbackFunc(a, b) {
console.log(a); // first
console.log(b); // second
}
clearInterval(intervalID); // clearInterval() 실행 시 반복 호출이 멈춤
setInterval()
함수는 한 번 설정되면 clearInterval()
함수를 호출하지 않는 이상 변화되지 않으며 초기의 값을 계속해서 기억하고 참조한다💡 React의
useEffect
와 JavaScript의setInterval
를 활용하여 3초마다 count라는 상태 값이 1씩 증가하는 코드를 작성하려는 시도를 해보자
#1 의존성 배열 ❌ (매 렌더링마다 실행)
clearInterval()
과 setInterval()
의 호출 타이밍이 어긋날 수 있는 문제 존재setInterval()
이 제대로 동작할 기회를 얻지 못할 수 있음import { useState, useEffect } from "react";
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 3000);
return () => clearInterval(timer);
});
return <span>{count}</span>;
}
export default App;
#2 빈 의존성 배열 ⭕ (최초 마운트 시에만 실행)
count 값이 0, 1, 2, 3, ... 과 같이 증가하는 것이 아니라, 0, 1에서 멈추는 현상 나타남
useEffect가 (첫 렌더에서 count의 초기값을 잡아버리고) 재실행되지 않으면 setInterval()
에 있는 클로저는 항상 첫 렌더의 count 값(0)를 참조하며 count+1은 항상 1이 되는 것
import { useState, useEffect } from "react";
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 3000);
return () => clearInterval(timer);
}, []);
return <span>{count}</span>;
}
export default App;
setCount(count + 1)
을 setCount(count => count + 1)
과 같이 수정const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setInterval(() => {
// setCount(count + 1);
setCount(count => count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <span>{count}</span>;
}
const App = () => {
const [count, dispatch] = useReducer((state, action) => {
if (action === "increment") {
return state + 1;
}
}, 0);
useEffect(() => {
let timer = setInterval(() => {
dispatch("increment");
}, 1000);
return () => clearInterval(id);
}, []);
return <span>{count}</span>;
}
useRef()
는 current 속성을 가진 객체를 반환하는데, current 속성의 값은 다음과 같은 특징을 가짐setInterval()
에 들어가는 callback 함수를 이 current에 저장하고 해당 값을 바꿔주면 계속해서 변화하는 (count가 변하여 리렌더링이 발생할 시 새로운 state와 props를 거쳐서 만들어지는) 콜백 함수를 반영할 수 있음useRef()
를 활용하여 저장해둔 savedCallback
덕분에 항상 최근 렌더링 후 세팅한 콜백 함수를 읽어올 수 있음// useInterval.js
import { useEffect, useRef } from "react";
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
// cf. delay 인자에 null 값을 전달할 경우 타이머를 멈출 수 있음
if (delay === null) return;
const timer = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(timer);
}, [delay]);
}
export default useInterval;
// App.js
import useInterval from "./useInterval";
const App = () => {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(count + 1);
}, 3000);
return <span>{count}</span>;
}