React와 척지고 싸우기 - 여러 함수를 몇 초마다 순서대로 실행하기
토이 프로젝트니까 이딴 짓을 하고 있는 거다. 현업 가면 아마 절대 안 할거야...
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만 따라 움직이는 거였으니까.
내 부족한 머리로는 이것보다 나은 솔루션을 찾을 수가 없다.
애초에 웹페이지 만들 때 무언가가 사용자의 입력 없이 순서대로 천천히 진행되어야 할 이유가 없으니까.