setInterval은 호출 스케줄링이다 :
일정한 시간이 지난 후에 원하는 함수를 호출하는 메소드이다.
호출 스케줄링은 setTimeout이라는 메소드도 있지만
setInterval은 주기적으로 실행해서 일정한 간격을 만드므로,
clearInterval을 이용해 함수호출을 중단해야 한다.
지연간격이란? delay와 같은 말이다.
만약 3초 간격으로 지연간격을 설정해 놓아도 setInterval은 3초 간격으로 함수를 호출하지 못할 가능성이 높다. 열린교회 닫힘 같은 소리지만 setInterval은 함수를 실행하는 시간도 지연간격에 포함된다는 소리다. (예시 : 함수를 실행하는데 1.5초가 걸리면 setInterval은 1.5초만에 실행이 된다.)
또한 setInterval은 clearInterval이 호출되기 전까지 메모리에 유지된다는 성격이 있다.
setInterval의 특성 탓에 React 에서 사용하면 가장 먼저 맞닥드리게 되는 오류가 클로저 오류이다.
사실 useInterval 이라는 커스텀 훅을 만들지 않아도 useEffect와 setState에 콜백함수를 사용하면 해결할 수 있는 오류이지만 useInterval 이 주는 이점이 확실하다.
const ReadyTimer = () => {
const [timer, setTimer] = useState(0);
useEffect(() => {
const id = setInterval(()=> {
setTimer(ready => ready+1) //클로저 역할을 해주는
}, 1000);
return () => clearInterval(id);
});
return <p>{timer}</p>
}
(setTimeout을 중첩으로 사용해도 되지 않을까 싶다.
그런데 내가 setTimeout중첩 이전에 useInterval을 먼저 알아버려서...)
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
커스텀 훅이라 당연한 이야기인가 싶다.
사용할때는 이것만 쓰면된다.
function Counter() {
let [count, setCount] = useState(0);
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>;
}
내부 구조.
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();//클로저 역할을 해주는 useRef. 렌더를 해도 초기화 되지 않는다.
// callback(setCount)가 변경될 때를 useEffect가 감지해서 최신상태를 저장한다.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// 인터벌과 클리어 세팅
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);//바로바로 클리어를 해주기 때문에 메모리를 차지하지 않는다.
}
}, [delay]);
}
대안 코드의 단점은 state가 아니라 props로 최신 값을 받아올때마다 렌더링을 하지만
useInterval은 useRef를 이용하기 때문에 렌더수를 줄일 수 있다.
delay를 매개변수로 받아 오기 때문에 delay를 수정시킬 수 있는 useInterval을 추가해주면 된다.
function Counter() {
const [delay, setDelay] = useState(1000);
const [count, setCount] = useState(0);
// 카운터 증가
useInterval(() => {
setCount(count + 1);
}, delay);
// 카운터 속도 증가
useInterval(() => {
if (delay > 10) {
setDelay(delay / 2);
}
}, 1000);
function handleReset() {
setDelay(1000);
}
return (
<>
<h1>Counter: {count}</h1>
<h4>Delay: {delay}</h4>
<button onClick={handleReset}>
Reset delay
</button>
</>
);
}