React - setTimeout을 이용한 배열 상태 업데이트

nearworld·2023년 1월 18일
0

개발 일지

목록 보기
5/7

기존 코드

const [arrState, setArrState] = React.useState([]);
const tempArray = [1, 2, 3, 4, 5];
React.useEffect(() => {
  const twoSec = 2000;
  tempArray.forEach((item, index) => {
    setTimeout(() => {
      setArrState([...arrState, item]);
    }, twoSec * index);
}, []);

arrState 상태가 계속 초기 [] 빈 배열에 있는 것을 확인했다.
리액트 공식 문서를 읽어보며 원인을 분석해본 결과는 이렇다.

위 코드의 forEach문을 풀어 써보면 아래와 같은 코드로 볼 수 있다.

setTimeout(() => {
  setArrState([...[], 1]); // 다음 렌더링 시에 빈배열 arrState에 1 추가
}, 2000 * 0);
setTimeout(() => {
  setArrState([...[], 2]); // 다음 렌더링 시에 빈배열 arrState에 2 추가
}, 2000 * 1);
setTimeout(() => {
  setArrState([...[], 3]); // 다음 렌더링 시에 빈배열 arrState에 3 추가
}, 2000 * 2);
setTimeout(() => {
  setArrState([...[], 4]); // 다음 렌더링 시에 빈배열 arrState에 4 추가
}, 2000 * 3);
setTimeout(() => {
  setArrState([...[], 5]); // 다음 렌더링 시에 빈배열 arrState에 5 추가
}, 2000 * 4);

arrState 배열에 값이 누적되지 않는 이유다. 위 코드가 실행 될 때의 렌더링에서는 arrState 값이 [] 이기 때문이다.

문제해결 한 코드

const [arrState, setArrState] = React.useState([]);

React.useEffect(() => {
  const twoSec = 2000;
  tempArray.forEach((item, index) => {
    setTimeout(() => {
      setArrState(prev => [...prev, item]);
    }, twoSec * index);
}, []);

상태 업데이터 콜백함수를 이용하면 매번 setTimeout의 콜백함수가 실행될때마다 arrState의 이전 상태를 이어서 업데이트 가능

더 들여다보기

setTimeout 함수를 쓰지 않는 경우에는 어떨까..

export default function App() {
  const [arr, setArr] = React.useState([]);
  const tempArr = [1, 2, 3, 4];
  React.useEffect(() => {
    tempArr.map((item, index) => {
      setArr([...arr, item]);
    })
  }, [])
  console.log(arr);
  return (
    <main>
      {arr.map((item) => <div>{item}</div>)}
    </main>
  )
}
React.useEffect(() => {
  setArr([...[], 1]); // 다음 렌더링 때 arr배열에 1추가
  setArr([...[], 2]); // 다음 렌더링 때 arr배열에 2추가
  setArr([...[], 3]); // 다음 렌더링 때 arr배열에 3추가
  setArr([...[], 4]); // 다음 렌더링 때 arr배열에 4추가
}, [])

이 코드에서 마지막에 처리된 setArr은 빈 배열에 4를 추가하고 있다. 리액트는 상태 스냅샷을 계속 업데이트 할 것인데 스냅샷들이 덮어 씌워져 마지막의 4를 추가하는 스냅샷이 다음 렌더링때 반영된다.

그렇지만 setTimeout으로 인해 일정 시간후 setState가 호출되는 코드라면 이미 렌더링이 일어난 후 다음 렌더링때 상태를 업데이트하라고 리액트에 요청하기 때문에 리렌더링이 setTimeout이 호출되는 만큼 발생하게 된다.

export default function App() {
  const [arr, setArr] = React.useState([]);
  const tempArr = [1, 2, 3, 4];
  React.useEffect(() => {
    tempArr.map((item, index) => {
      setTimeout(() => {
        setArr([...arr, item]);
      }, index)
    })
  }, [])
  console.log(arr);
  return (
    <main>
      {arr.map((item) => <div>{item}</div>)}
    </main>
  )
}

위 코드는 4개의 setTimeout에 걸린 timeout이 각각 0, 1, 2, 3, 4 밀리초로 설정된다.
코드를 실행해 본 결과, 시간이 너무 짧아 마지막 결과인 4만 화면에 보일 뿐이다.
콘솔에는 [] [1] [3] [4] 만 찍히는데 2밀리초 후에 실행되는 setArr은 무시된 걸 알 수 있었다. 현재 개발 환경의 리액트에서 arr의 값이 [1] 인 렌더링까지 걸린 시간이 2밀리초 이상 3밀리초 이하라는 것을 알 수 있는 부분이었다.

만약 위 코드를 굉장히 빠르게 여러번 재실행해보면 [2]가 포함될 때도 있다.
이게 실행 시점에서 코드의 처리 속도가 달라질 수 있다는 것을 증명하는게 아닐까 싶다.

profile
깃허브: https://github.com/nearworld

0개의 댓글