Debounce 도입 배경
아래 화면은 루빗 앱에서 진행되는 회원가입의 최종 화면이다.
- "이대로 시작할래" 버튼을 누를 때 회원가입 API 호출이 일어난다.
- API 호출 결과, DB에 User가 하나 생성된다.
- 이 때 "이대로 시작할래" 버튼을 여러번 누른다면? User가 여러개 생성된다.
- 그렇다면 User가 여러개 생성되는 것을 방지해야한다.
- 방지하기 위해 버튼을 누르는 순간 loading state를 true로 만들어 해당 버튼을 비활성화 시킨다.
위 조건을 코드로 구현하면 아래와 같다.
const [totalSignupLoading, setTotalSignupLoading] = useState<boolean>(false);
/** 회원가입 */
const signup = () => {
setTotalSignupLoading(true); // 회원가입 API 호출 전, loading state를 true로 설정.
// call signup api
// after signup api
setTotalSignupLoading(false); // 회원가입 API 결과 받은 후, loading state를 false로 설정.
};
/** 버튼 */
const SubmitButton = () => {
return (
<Button disabled={totalSignupLoading} title="이대로 시작할래!" onPress={signup} />
);
};
그런데.. 이렇게 하는데도 loading을 true로 만들어주기 직전에 버튼을 와다다다 눌러버리면 여전히 중복 호출은 존재한다. (많지는 않다.)
-> 버튼 비활성화를 state로 관리하기에는 완벽하지 않다.
그러다가, 참여하고 있는 개발스터디에서 debounce 개념을 알게 되었다.
debounce란,
- 반복적인 특정 동작을 반복되는 과정에서 강제적으로 대기하는 것이다.
- 연속적으로 발생한 이벤트를 하나의 그룹으로 묶어서 처리하는 방식으로, 주로 그룹에서 처음이나 마지막으로 실행된 함수를 처리하는 방식으로 사용된다.
사용해보자!
import _ from "lodash";
/** 회원가입 */
const signup = _.debounce(() => {
// call signup api
}, 1000);
/** 버튼 */
const SubmitButton = () => {
return (
<Button title="이대로 시작할래!" onPress={signup} />
);
};
-> 이렇게 사용하면 signup 함수는 1초를 기다린 후에 실행된다.
첫호출 이후 1초동안의 함수호출을 막을 수는 없을까? 가능하다.
import _ from "lodash";
/** 회원가입 */
const signup = _.debounce(() => {
// call signup api
}, 1000, { leading: true, trailing: false });
/** 버튼 */
const SubmitButton = () => {
return (
<Button title="이대로 시작할래!" onPress={signup} />
);
};
```{ leading: true, trailing: false }```
옵션을 사용하면 된다.
우선은 여기까지만 읽으면 대부분의 경우에는 정상동작한다.
하지만 루빗앱에는 도입하지 못했는데, 주의점을 보자.
⭐️주의점 (중요)
만약 debounce를 사용하려는 함수 내부에서 state의 변경으로 컴포넌트가 재렌더링된다면,
debounce 함수 역시 재생성된다. 그러므로 useCallback을 사용해야 한다.
최종으로는 debounce를 사용하지 못했다.
useCallback 사용을 시도했다가, 구독해야하는 변수가 너무나도 많아 포기...
signup 함수에서는 딱 signup api 호출만 있는 것이 바람직하지만,
현재는 여러 단계의 함수 호출이 있어서 당장은 리팩토링하기보다 state로 만족하기로 했다.
다음에 debounce를 사용해볼만한 곳에서 사용해보기로 한다.
또한 debounce를 사용할 함수에서는 argument를 받아서 구독이 필요없는 useCallback(() => , []) 을 사용할 수 있도록 하자. (관리하기 편하니까)
현재는 state로 중복호출이 99% 제어되는 것으로 만족하자...!
우리 signup 함수는 나중에 리팩토링 하자.
(+ 230312 추가)
현구님이 댓글을 달아주셨는데, 내가 겪었던 문제를 해결해줄 수 있는 useEvent라는 기능의 도입이 논의되고 있다. (아직 RFC)
요약하자면 useCallback으로 종속성 관리를 하지 않아도, useEvent가 항상 함수를 같은 주소에 저장하면서 최신 상태를 유지해주는 것이다.
(이름은 useEvent, useGlobal, useStatic 등 다양한 논의가 진행되고 있는 것 같다!ㅋㅋ)
어쨌든 debounce함수는 사용하기 정말 편하다! 꼭꼭 알아두고 써먹어보자.
debounce와 비슷한 throttle 도입도 조만간 올려보겠다!
참고
https://www.mrlatte.net/code/2020/12/15/lodash-debounce
'Development' 카테고리의 다른 글
파일구조 예쁘게 보는 법: 파일트리 (tree) (0) | 2023.07.26 |
---|---|
개념 정리: CSR, SSR, 돔트리, HOC, Hook, ... (작성중) (1) | 2023.07.25 |
구글 플레이스토어 주문 ID 해석하기 (.. 뒤에 붙은 숫자의 비밀!) (0) | 2023.02.23 |
dayjs 제대로 활용하기: getTimezoneOffset (0) | 2023.02.17 |
dayjs.tz() 사용 시 주의할 점: timezone 등록 (Android Hermes) (0) | 2023.02.17 |