Section19 Debounce와 Throttle

sihyun·2024년 4월 3일

개요

Debounce와 Throttle은 둘 다 이벤트 핸들링에 사용되는 기술로, 이벤트가 발생할 때 함수의 실행을 제어하는 방식을 말한다. 주로 사용자 인터렉션과 같이 빈번하게 발생하는 이벤트를 다룰 때 사용된다.

  • Debounce : 특정 기간 안의 함수 실행을 모두 취소하고 마지막에만 실행한다.
  • Throttle : 함수 실행 후 특정 기간 동안의 추가 실행을 모두 취소한다.

Flutter에서는 debounce_throttle 패키지를 추가해 추가 구현 없이 사용할 수 있다.
https://pub.dev/packages/debounce_throttle
코드로 적용한 모습을 보면 이해가 편하다.

Throttle

Throttle이 필요한 이유

Throttle은 이벤트를 일정 주기로 제한하여, 과도한 이벤트 핸들링으로 인한 성능 저하를 막아준다.

메인 화면에서는 식당 리스트를 가져온다. 마지막 리스트까지 읽으면 pagination함수를 실행해 추가로 식당 리스트를 서버에서 가져온다. 이때 서버에서 데이터를 가져와서 화면에 반영하는 시간! 이 잠깐의 시간에 한번 더 요청이 된다. 이런 경우에 Throttle을 사용하면 스크롤 이벤트가 일어난 뒤 일정 시간 동안은 같은 이벤트가 일어나지 않도록 할 수 있다. 특정 시간 동안 중복으로 데이터가 요청되는 것을 방지할 수 있다.

적용

class _PaginationInfo {
  final int fetchCount;
  final bool fetchMore;
  final bool forceRefetch;

  _PaginationInfo({
    this.fetchCount = 20,
    this.fetchMore = false,
    this.forceRefetch = false,
  });
}

class PaginationProvider<T extends IModelWithId,
        U extends IBasePaginationRepository<T>>
    extends StateNotifier<CursorPaginationBase> {
  final U repository;
  // Throttle 정의
  final paginationThrottle = Throttle(
    const Duration(seconds: 3),
    initialValue: _PaginationInfo(),
    checkEquality: false,
  );

  PaginationProvider({required this.repository})
      : super(CursorPaginationLoading()) {
    paginate();

		//throttle linstener 등록
    paginationThrottle.values.listen((state) {
      _throttledPagination(state);
    });
  }

  Future<void> paginate({
    int fetchCount = 20,
    bool fetchMore = false,
    bool forceRefetch = false,
  }) async {
    paginationThrottle.setValue(_PaginationInfo(
      fetchMore: fetchMore,
      fetchCount: fetchCount,
      forceRefetch: forceRefetch,
    ));
  }

  _throttledPagination(_PaginationInfo info) async {
	  // 실제 Pagination Logic  
  }
}
  • Throttle 정의
    • duration : Throttle 시간
    • initialValue : throttle을 사용할 함수에서 필요한 변수 초기화
    • checkEquality : 새로운 값이 들어왔을 때만 변경한다.
  • 코드 설명
    • 생성자에서 생성한 throttle(paginationThrottle)에 Stream의 값을 등록.
    • listen함수의 state는 throttle에서 사용할 _PaginationInfo이다. initialValue의 값.
    • paginate함수가 실행되어 paginationThrottle.setValue()가 실행될 때마다 throttledPagination(state) 함수를 호출하여 페이징 작업을 실시한다.

Debounce

Debounce가 필요한 이유

Debounce는 입력이 중복되는 것을 방지하고, 사용자가 연속으로 입력을 할 때 입력의 마지막이 이벤트로 처리되고 이전 입력을 무시하기 위해 사용된다.

예를들어 검색을 생각해 보면, 사용자가 검색창에 글자를 입력할 때마다 검색을 실행하는 것이 아니라, 사용자가 일정 시간 동안 입력을 멈춘 후에 검색을 실행한다. 이는 사용자가 입력하는 중에 불필요한 검색 요청을 줄여주고, 마지막으로 입력된 텍스트에 기반한 검색을 보장할 수 있다.

또 이번 앱에서는 사용자가 장바구니에 물품을 추가했을 때 즉시 서버에 장바구니 데이터 Patch 요청을 보낸다. 장바구니에 물품을 추가하는 행위를 즉시 반영할 필요가 없을 뿐더러 효율성이 떨어지게 된다. 이런 상황에서 첫 이벤트가 발생한 후 일정 시간을 기다린 뒤 그 명령을 실행하면 그 동안 쌓인 명령이 한번에 서버에 적용된다.

적용

class BasketProvider extends StateNotifier<List<BasketItemModel>> {
  final UserMeRepository repository;
  
  // Debouncer 정의
  final updateBasketDebounce = Debouncer(
    const Duration(seconds: 3),
    initialValue: null,
    checkEquality: false,
  );

  BasketProvider({required this.repository}) : super([]) {
	  // 리스터 등록
    updateBasketDebounce.values.listen((event) {
      patchBasket();
    });
  }

  Future<void> patchBasket() async {
      // PATCH 로직
  }

  Future<void> addToBasket({
    required ProductModel product,
  }) async {
    // 추가 로직

		// 리스너 호출
    updateBasketDebounce.setValue(null);
  }

  Future<void> removeFromBasket({
    required ProductModel product,
    bool isDelete = false,
  }) async {
    // 감소 로직

		// 리스너 호출
    updateBasketDebounce.setValue(null);
  }
}
profile
주니어 Flutter 개발자

0개의 댓글