본문 바로가기

공부기록/Typescript

#76

setInterval은 useEffect안에 넣자


 

setState로 상태 업데이트를 하게 되면

컴포넌트를 다시 실행되고,

컴포넌트가 실행될 때마다 새 interval이 실행될 것이다. 

이전 interval도 계속 실행되어 실제로 무한루프에 빠진다. 

 

 

 setInterval(function () {
      setRemainigTime((prev) => prev - 50);
}, 50);

 

 

그러므로 useEffect에 넣고 뎁스를 비워두면 

컴포넌트가 렌더링 된 뒤 setInterval은 한번만 실행된다. 

 

 

 

 

import Container from "./UI/Container.tsx";
import { type Timer as TimerProps } from "../store/timers-context.tsx";
import { useEffect, useState } from "react";
import { useRef } from "react";

export default function Timer({ name, duration }: TimerProps) {
  const [remainingTime, setRemainigTime] = useState(duration * 1000);

  useEffect(() => {
    setInterval(function () {
      setRemainigTime((prev) => prev - 50);
    }, 50);
  }, []);

  const formattedRemainingTime = (remainingTime / 1000).toFixed(2).toString();
  console.log(formattedRemainingTime);

  return (
    <Container as="article">
      <h2>{name}</h2>
      <p>
        <progress max={duration * 1000} value={remainingTime} />
      </p>
      <p>{formattedRemainingTime}</p>
    </Container>
  );
}

 

 

타이머의 숫자가 0보다 작아지면 타이머를 clear해보자


 

 

setInterval함수는 타이머 참조를 반환하며, 

주어진 타이머 또는 간격을 고유하게 식별하는 숫자 유형의 값을 반환한다.

그러므로 아래와 같이 clearInterval로 활용할 수 있다.

 

 

  if (remainingTime <= 0 && interval.current) {
    clearInterval(intervalId);
  }

  useEffect(() => {
    const intervalId = setInterval(function () {
      setRemainigTime((prev) => prev - 50);
    }, 50);
  }, []);

 

 

 

그런데 여기서 문제점은 

여러 컴포넌트가 생길 때마다 해당 변수가 다시 만들어진다

 

 

 

그럼 컴포넌트 함수 외부로 이동시킨다면?


Timer들을 여러개 사용하고 있는데 

타이머 컴포넌트의 모든 인스턴스에서 공유된다.

 

한개를 지우면 모두 다같이 지워진다.

 

 

 

 

ref를 활용


ref에 타이머 ID를 저장 

초깃값 null,

그리고 

useEffect에서 interval.current가 setInterval에 의해 

반환된 값으로 설정.

 

 

 

  if (remainingTime <= 0 && interval.current) {
    clearInterval(interval.current);
  }

  useEffect(() => {
    interval.current = setInterval(function () {
      setRemainigTime((prev) => prev - 50);
    }, 50);
  }, []);

 

 

 

ref의 타입설정


 

  const interval = useRef<number | null>(null);

 

 

초기엔 null값이며, number또는 null이 될 수 있고, 

DOM요소에 ref를 추가할때는 react가 알아서 작업을 해 줄 것이지만, 

우리가 다시 설정하는 부분 (여기서 ref.current에 아이디값을 할당)

에서는 직접 해줘야 한다.  

 

 

 

 if (remainingTime <= 0 && interval.current) {
    clearInterval(interval.current);
  }

  useEffect(() => {
    interval.current = setInterval(function () {
      setRemainigTime((prev) => prev - 50);
    }, 50);
  }, []);

 

 

clearInterval(ref.current)은 정상작동하지 않는다


 

React strict mode 때문에.


strict mode가 하는 일은

컴포넌트 함수가 렌더링될 때마다 해당 함수를 두 번 실행

 

따라서 현재 useEffect가 두 번 실행되므로

이 컴포넌트가 기본적으로 마운트되고 나서 즉시 언마운트되고 다시 마운트됨

 

러나 첫 번째 인스턴스가 언마운트될 때 이 타이머, 이 간격이 멈추지 않음

 

 

 

 

useEffect의 return


 

 

useEffect에서 값을 반환할 수 있으며 

함수 또는 아무것도 아니어야 함

 

함수를 반환하면 정리함수가 됨

 

 

 

  useEffect(() => {
    interval.current = setInterval(function () {
      setRemainigTime((prev) => prev - 50);
    }, 50);

    return () => clearInterval(interval.current);
  }, []);

 

 

 

에러


 

 

useEffect에서 interval.current와 

return함수의 interval.current은 이론적으로는 다를 수 있다. 

 

실제로는 useEffect가 실행된 이후 실행될 수 있음 

 

해결방법은 


useEffect안에서 timer라는 상수를 만들고 그것을 

내부적으로 return하는 함수에서 사용하는 것이 좋음 

 

 

ref에도 해당 상수를 사용

 

 

 

  useEffect(() => {
    const timer = setInterval(function () {
      setRemainigTime((prev) => prev - 50);
    }, 50);

    interval.current = timer;

    return () => clearInterval(timer);
  }, []);