react hooks component에서 setInterval 사용의 문제점

useInterval이라는 커스텀 훅이 있다.

이 훅은 interval을 세팅하고, 컴포넌트가 언마운트 될 때 interval을 clera한다.
이렇게 함으로서 setInterval과 clearInterval 모두 컴포넌트 라이프 사이클에 종속된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { useState, useEffect, useRef } from "react";

function useInterval(callback, delay) {
const savedCallback = useRef();

// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);

// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}

setInterval + clearInerval의 단점

임피던스 불일치라는 용어를 쓴다.
리액트 프로그래밍 모델은 state를 기반으로 렌더링되고 기타 변수들을 관리한다.

하지만 setInterval은 일단 설정하고 나면 없애는 것 외에는 변경할 수 없다. 또한 react의 state 기반으로 동작하지도 않는다.

state나 props가 변하더라도 교체하기 전까지는 내부의 props와 state를 참조할 것이다.

ref의 사용

위에서 알아본 것 처럼 setInterval은 react의 동작과 다르게 동작한다.
이로 인해 발생하는 문제점은 다음과 같다.

  1. 첫 렌더에서 callback1을 가진 setInterval(callback1,delay)를 수행한다.
  2. 다음 렌더에서 새로운 props와 state를 거쳐서 만들어지는 callback2가 있다. 하지만 시간을 재설정하지 않고서는 callback을 대체할 수 없다.

이를 해결하려면 useRef를 사용하면 된다.

useRef는 hooks에서 렌더링과 관련없는 변수를 담는데에 이용되는 하나의 상자이다.

솔루션은 다음과 같다.

  1. setInterval(fn,delay)에서 함수가 savedCallback을 호출하게 만든다.
  2. 첫 렌더링에서 savedCallback을 callback1로 설정한다.
  3. 두 번째 렌더링에서 savedCallback을 callback2로 설정한다.

결과본은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { useState, useEffect, useRef } from "react";

function useInterval(callback, delay) {
const savedCallback = useRef();

// callback에 변경사항이 있을 경우 가장 최근의 함수를 저장한다
// 최근의 state나 props의 변경사항을 반영한 callback이다.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);

// 타이머를 교체하거나 하는 작업 없이 제일 최근에 들어온 함수를 실행한다.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}

Interval 일시정지하기

위의 useInterval hook에선 null을 delay에 전달하는 것으로 interval을 일시 정지할 수 있다.
delay가 null이 아닐경우 타이머를 설정하기 떄문이다..!

1
2
3
4
5
6
7
8
9
const [delay, setDelay] = useState(1000);
const [isRunning, setIsRunning] = useState(true);

useInterval(
() => {
setCount(count + 1);
},
isRunning ? delay : null
);

ref

훅스 컴포넌트에서 setInterval 사용시의 문제점
Making setInterval Declarative with React Hooks

댓글