데이터 요청 후 응답 전까지 버튼 비활성화 하기
public API로 파이어베이스 연습 도중 버튼 중복 클릭을 막아야 겠다는 생각이 들었다.
이유는 아래 "강쥐 사진 보기" 버튼을 클릭하면 데이터를 불러 올 동안 빈화면이 나오는 데, 이때 버튼이 활성화 되어있다는 거.
그럼 버튼을 광클할 수 있고, api 요청을 여러번 할 수 있다. 아직 첫 번째 데이터를 불러오지도 못했는데 여러번 api를 요청해버리면 강아지 사진이 빠르게 연속적으로 지나가버린다. 이를 막기 위해 데이터를 받아오기 전까지는 버튼을 비활성화하는 방법을 구현해봤다.


리액트에서 useState로 isChange이라는 boolean 값을 주고 버튼을 누르면 버튼이 비활성화되고(true가 버튼 비활성화) 데이터 get 작업이 끝난 후 다시 버튼을 활성화(false로 스위치) 시키는 방법을 사용했다.
const [isChange, setIsChange] = useState(false);
const getAPI = async () => {
setIsChange(true);
const result = await axios.get("https://dog.ceo/api/breeds/image/random");
console.log(result);
setApiData(result.data.message);
setIsChange(false);
};
const onClickChangeData = () => {
getAPI();
};
해보니 버튼 비활성화가 잘 된다.

?? 왜 잘 되는걸까?
분명 useState의 set은 함수가 모두 끝난 뒤에 마지막에 담긴 값을 리렌더링해서 보여주는 작동 원리를 가지고 있다.
근데 한 함수내에 두번의 setIsCahnge를 했음에도 버튼 활성화와 비활성화가 잘 작동한다?
Micro Task Queue vs Macro Task Queue
이는 마이크로 태스크 큐와 매크로 태스크 큐 때문이다.
await를 하게 되면 call stack에 들어간 실행 함수가 큐(queue)로 넘어가서 대기 상태에 놓이게 된다.
이때 두 가지 종류의 큐로 구분되어 담기게 되는데, 하나는 매크로태스크 큐이며 다른 하나는 마이크로태스크 큐다.
어떤 기준으로 나눈 것인지 궁금해서 찾아봤다. 마이크로 태스크 큐와 관련된 기준은 아래와 같다.
micro queuePromise : promise가 해결되거나 거부되면 해당 콜백이 마이크로 태스크에 추가 된다.
Mutation Observer : DOM이 변경되면 마이크로 태스크에 추가 된돠.
Process.nextTick() : node.js 관련 콜백으로 마이크로태스크에 추가된다.
Promise, process.nextTick, MutationObservermacro queue
setTimeout() ,setInterval() : 매크로태스크 큐에 추가된 후 일정 시간이 지나면 실행된다.
I/O : 네트워크 요청, 파일 시스템 작업, 기타 I/O 작업은 매크로태스크 큐에 추가된다.
UI rendering : UI 렌더링도 매크로 작업 대기열에 추가된다.
출처 : geeksforgeeks
setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI 렌더링
두 태스크 큐에는 우선순위가 있는데 마이크로 태스크 큐가 우선 실행되며 마이크로 태스크 큐가 모두 실행되고나면 매크로 태스크 큐가 실행 된다.
실행 우선 순위 : 마이크로 태스크 큐 > 매크로 태스크 큐
그렇다면 이런 생각이 든다.
1. 빠른 실행을 위해 모두 마이크로 태스크 큐에 넣으면 좋지 않을까?
2. 최대한 빠르게 실행되어야 하는 함수를 마이크로 태스크 큐에에 넣어야 겠네?
(이런 내 생각은 대체로 정답이었던 적이 없었다)
조금 찾아보니 micro queue의 경우 시스템 내의 개별 큐의 성능에 초점을 맞춘 기준이고,
macro queue의 경우 전체 큐잉 시스템 성능에 초점을 맞춘 기준이라고 한다. (출처는 chat GPT임으로 정확하지 않을 수 있지만?)
위의 사실이 맞다는 가정이라면 각각의 태스크 큐는 장단점이 존재할 것이다.
예로 micro queue는 각 대기열의 효율성과 효과를 우선시 하기 때문에 평균 대기 시간, 각 대기열에 있는 고객 수, 시간 단위당 서비스를 제공하는 시스템에서 대기 시간을 단축하기 위해 쓰면 좋을 것이고,
macro queue의 경우 각 전체 대기 시간을 줄이거나 전체 처리량을 늘리는 등 시스템 수준 성능 문제를 해결하는데 유용할 것이다.
(이 또한 chatGPT의 답변을 참고한 것이니 정확하지 않을 수 있다)
다시 돌아와서,
강아지 사진 데이터를 요청할 때 await를 사용했다. 자바스크립트는 await를 만나는 순간 async를 감싸고 있는 function이 작업을 중단하고 마이크로 큐로 들어가게 된다. 그리고 이후 마이크로 큐에서 빠져나와 실행될 때에 기억한 위치부터 실행이 된다.
setIsChange(true) 실행 -> await로 인해 함수가 콜스택에서 마이크로 큐로 들어감 -> 마이크로 큐에서 빠져나와 다시 콜스택으로 들어감 -> 함수가 다시 실행되면서 아래에 있는setIsChange(false) 실행
useState는 함수가 모두 종료되었을 때 리렌더링 된다. 그러니까 콜스택이 모두 비워졌을 때 리렌더링이 이루어진다는 것인데. 마이크로 큐로 들어가면서 콜스택이 모두 비워면서 1차 리렌더링 발생, 그리고 마이크로 큐에 있던 함수가 다시 콜스택으로 들어가서 실행되면 콜스택이 비워지기 때문에 2차 리렌더링이 발생한다는 것.
이런 원리로 버튼이 막아졌던 것...!
콜스택과 큐, 함수 작동원리는 언제봐도 재밌다.
'프로그래밍⚡️ > React' 카테고리의 다른 글
재사용성을 높이는 디자인과 컴포넌트에 대한 이야기 (1) | 2023.03.19 |
---|---|
React-hook의 종류와 custom hook 사용기 (0) | 2023.02.24 |
리액트 변수 State '간단' '쉽게' 정리 (0) | 2023.01.30 |
container/presentational 패턴 사용 후기 (0) | 2023.01.23 |
React, Import와 Export (0) | 2023.01.15 |