카테고리 없음

React와 척지고 싸우기 - 여러 함수를 몇 초마다 순서대로 실행하기

hyuckkim 2023. 11. 27. 18:59

토이 프로젝트니까 이딴 짓을 하고 있는 거다. 현업 가면 아마 절대 안 할거야...

 

async () => {
  setState(func1(state));
  await waitOneSeconds();
  setState(func2(state));
  await waitOneSeconds();
  setState(func3(state));
}

처음에는, button.onClick에 이런 느낌의 함수를 넣으면 될 거라고 생각했었다.

상태가 바뀌고, 1초 후에 상태가 바뀌고, 1초 후에 상태가 바뀐다. 완벽하다!

 

...리액트 최적화는 그렇게 간단하지 않았다. async / await 함수라도, 그 함수가 모두 끝난 이후에 state가 반영된다.

심지어, 이 코드는 예상대로 작동하지도 않는다. setState는 맨 마지막에 적용되기 때문에, state를 바꾸는 함수 3개가 모두 실행되지도 않는다.

 

async () => {
  let newState = state;
  newState = func1(newState);
  await waitOneSeconds();
  newState = func2(newState);
  await waitOneSeconds();
  newState = func3(newState);
  
  setState(newState);
}

그래서 임시방편으로 이렇게 해 두고 잠을 잤다.

자고 일어나면 더 멋진 솔루션이 생각나지 않을까 해서.

 

 

 

...

 

 

 

자고 일어났다.

함수가 전부 끝나야 평가식이 바뀐다면, 함수가 3번 작동하도록 만들면 되지 않을까?

const [queue, setQueue] = useState<Promise<void>[]>([]);

useEffect(() => {
  ...
}, [queue]);

() => {
  setQueue([...queue, func1(state)]);
  setQueue([...queue, func2(state)]);
  setQueue([...queue, func3(state)]);
}

대충 이런 느낌으로 Promise를 3번 적용하면 되지 않을까?

이건 구현도 안 됐다. 애초에 저러면 func1-2-3에 들어가는 state가 모두 같아서 의미가 없고, useState에 Promise를 넣는다고 useEffect에서 기다릴 수도 없다. 애초에 useEffect 자체가 async를 못 쓴다.

그래도 queue를 쓴다는 아이디어는 나쁘지 않은 것 같다.

 

 

 

type queueData = {time: number} & (func1Data | func2Data | func3Data)

type func1Data = {type: "func1Data"}
type func2Data = {type: "func2Data", other: number}
type func3Data = {type: "func3Data", another: string, theOther: heavyData}

const [queue, setQueue] = useState<queueData[]>([]);

useEffect(() => {
  const current = queue[0];
  if (!!queue) {
    setTimeout(() => {
      switch (current.type) {
        case "func1Data":
          setState(func1(state));
          break;
        case "func2Data":
          setState(func2(state, current.other));
          break;
        case "func3Data":
          setState(func3(state, current.another, current.theOther));
          break;
        }
        setQueue(prev => prev.slice(1));
      }, current.time);
    }
  }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queue]);

() => {
  setQueue(prev => [...prev, {time: 1000, type: "func1Data"}]);
  setQueue(prev => [...prev, {time: 500, type: "func2Data", other: 1}]);
  setQueue(prev => [...prev, {time: 250, type: "func3Data", another: "save", theOther: {}}]);
}

 

최종적으로는 이런 코드가 되었다.

어떻게든 func1-2-3들을 순수함수로 만들고, 인수들을 각각 타입으로 만들어줬다.

queue가 수정되면 useEffect 맨 앞의 값 하나만 해결해 없애지만, 그 실행이 끝나면 queue가 수정되었기 때문에 모든 함수가 순서대로 실행된다.

useEffect는 queue 요소의 개수만큼 실행되었으니까, '함수 하나가 끝나면 재랜더링된다!' state 바뀐게 바로 반영된다.

 

lint는 state에가 바뀔 때에도 useEffect가 실행되어야 한다고 할 것이다. 원래 원칙적으로 그게 맞긴 한데 이 경우에는 아니다. 이 useEffect는 queue만 따라 움직이는 거였으니까.

 

내 부족한 머리로는 이것보다 나은 솔루션을 찾을 수가 없다.

애초에 웹페이지 만들 때 무언가가 사용자의 입력 없이 순서대로 천천히 진행되어야 할 이유가 없으니까.