Debounce로 버튼 중복호출 막기 (useCallback 사용해 함수재생성 방지까지)

Dahee Ahn
roubit.me
Published in
6 min readMar 5, 2023

안녕하세요 루빗 개발팀 보라입니다😊
그동안 블로그 업로드가 뜸했죠!
앞만 보고 달려온 우리팀… 이제는 발자취도 남겨야겠다 싶어,
1주일 1블로그 업로드를 목표로 삼았습니다!

쓰는 동안은 참 귀찮기도 하지만, 오래 두고 봤을 때
기록은 무한한 가치가 있답니다!

꾸준한 업로드 기대 부탁드리며,,, 허허
그럼 시작합니다!

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란,

  • 반복적인 특정 동작을 반복되는 과정에서 강제적으로 대기하는 것이다.
  • 연속적으로 발생한 이벤트를 하나의 그룹으로 묶어서 처리하는 방식으로, 주로 그룹에서 처음이나 마지막으로 실행된 함수를 처리하는 방식으로 사용된다.
출처: https://www.freecodecamp.org/korean/news/debounce-dibaunseu-javascripteseo-hamsureul-jiyeonsikineun-bangbeob-js-es6-yeje/

사용해보자!
lodash 라이브러리에 이미 debounce가 정의되어 있으므로, 해당 모듈을 깔고 import만 하면 된다. (lodash를 사용하지 않고도, debounce 함수를 직접 만들 수도 있다. )

import _ from "lodash";

/** 회원가입 */
const signup = _.debounce(() => {
// call signup api
}, 1000);

/** 버튼 */
const SubmitButton = () => {
return (
<Button title="이대로 시작할래!" onPress={signup} />
);
};

-> 이렇게 사용하면 signup 함수는 1초를 기다린 후에 실행된다.

첫호출 이후 1초동안의 함수호출을 막을 수는 없을까? 가능하다.

{ leading: true, trailing: false }

옵션을 사용하면 된다.

우선은 여기까지만 읽으면 대부분의 경우에는 정상동작한다.

하지만 루빗앱에는 도입하지 못했는데, 아래를 읽어보자.

⭐️주의점 (중요)

만약 debounce를 사용하려는 함수 내부에서 state의 변경으로 컴포넌트가 재렌더링된다면,

debounce 함수 역시 재생성된다. 그러므로 useCallback을 사용해야 한다.

참고링크

최종으로는 debounce를 사용하지 못했다.

useCallback 사용을 시도했다가, 구독해야하는 변수가 너무나도 많아 포기...

signup 함수에서는 딱 signup api 호출만 있는 것이 바람직하지만,

현재는 여러 단계의 함수 호출이 있어서 당장은 리팩토링하기보다 state로 만족하기로 했다.

다음에 debounce를 사용해볼만한 곳에서 사용해보기로 한다.

또한 debounce를 사용할 함수에서는 argument를 받아서 구독이 필요없는 useCallback(() => , []) 을 사용할 수 있도록 하자. (관리하기 편하니까)

(현재는 state로 중복호출이 99% 제어되는 것으로 만족…! 우리 signup 함수는 나중에 리팩토링 하자.)

어쨌든 debounce함수는 사용하기 정말 편하다! 꼭꼭 알아두고 써먹어보자.

debounce와 비슷한 trottle 도입도 조만간 올려보겠다!

참고

https://www.mrlatte.net/code/2020/12/15/lodash-debounce

https://kyounghwan01.github.io/blog/React/debounce/#react%E1%84%8B%E1%85%A6%E1%84%89%E1%85%A5-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5

https://code-designer.tistory.com/150?category=918902

👉보라 개인 기술블로그가 궁금하다면?

--

--