본문 바로가기

Flutter/Skills

Throttle 과 Debounce

 

앱 개발과 프론트엔드 실제 서비스 개발을 하다보면, 정말 많이 필요하고 사용하게될 개념이다.

 

Throttle 과 Debounce 모두, 무의미하게 무수하게 발생하는 이벤트를 막기 위한 기법이다.

  • Throttle : 함수 실행 후 특정 기간 동안 추가 실행을 모두 취소 (특정기간동안, 맨 처음 실행된것만 실행)
  • Debounce : 특정 기간안의 함수 실행을 모두 취소하고, 마지막에만 실행 (특정기간동안, 마지막 에 실행된것만 실행)

 

 

Throttle

throttle은 영어로 조르다, 목을 조르다라는 의미를 가지고 있다.

이벤트의 목을 조른다(?)라는 느낌으로 일단 이해를 하는 것이 좋다.

throttle 의 목적은 이벤트가 무수하게 발생하더라도, 일정 시간 동안 이벤트가 1번만 발생하도록 처리하는 것을 의미한다.

보통 앱개발에서 페이징, 스크롤 기능에 throttle 을 적용해서, '데이터 중복 요청방지' 기능을 보통 작업한다.
스크롤을 할 때 최적화하지 않으면, 이벤트가 무수히 많이 발생하고, 리스트 데이터가 꼬이고 엉망이 된다.

 

아래 예시 코드에서는:
  1. Throttle 클래스를 사용하여 5초의 지연 시간을 가진 쓰로틀 객체를 생성합니다.
  2. _incrementCounter 메서드에서 쓰로틀을 적용합니다. 이 메서드는 버튼이 눌릴 때마다 호출되지만, 실제로는 5초에 한 번만 카운터를 증가시킵니다.
  3. 사용자가 버튼을 빠르게 여러 번 눌러도 카운터는 5초에 한 번씩만 증가합니다.
이 방식으로 버튼 이벤트에 쓰로틀링을 적용하면, 사용자의 빠른 연속 클릭으로 인한 과도한 상태 업데이트나 불필요한 작업 실행을 방지할 수 있습니다.
 
import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ThrottleButtonExample(),
    );
  }
}

class ThrottleButtonExample extends StatefulWidget {
  @override
  _ThrottleButtonExampleState createState() => _ThrottleButtonExampleState();
}

class _ThrottleButtonExampleState extends State<ThrottleButtonExample> {
  int _counter = 0;
  final _throttle = Throttle(delay: Duration(seconds: 5));

  void _incrementCounter() {
    _throttle.run(() {
      setState(() {
        _counter++;
      });
      print('버튼이 눌렸습니다. 카운터: $_counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Throttle Button Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '버튼을 누른 횟수:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

class Throttle {
  final Duration delay;
  bool waiting = false;
  Timer? _timer;

  Throttle({required this.delay});

  void run(Function action) {
    if (!waiting) {
      action();
      waiting = true;
      _timer = Timer(delay, () {
        waiting = false;
      });
    }
  }
}
 

 


 

Debounce

이벤트가 무수히 발생하더라도 마지막 이벤트를 기준으로 일정 시간이 지나면 이벤트를 1번만 발생시키는 기법이다.

마지막 이벤트까지의 이벤트들을 하나의 그룹으로 생각해서, 한 개의 이벤트를 발생시키는 것이다.

다른 말로 말하면, 일정한 시간이 지나기 전에 이벤트가 계속 발생하면 이벤트를 막아버린다.

입력값 실시간 검색 기능 같은 곳에 많이 쓰인다.

 

 

이 예시 코드에서는:

  1. Timer? 타입의 _debounce 변수를 선언하여 디바운스 타이머를 관리합니다.
  2. _incrementCounter 메서드에서 디바운스를 구현합니다:
  3. 사용자가 버튼을 연속해서 빠르게 누르면, 마지막 클릭 후 500밀리초가 지난 후에만 카운터가 증가합니다.
  4. dispose 메서드에서 타이머를 취소하여 메모리 누수를 방지합니다.

방식으로 버튼 이벤트에 디바운스를 적용하면, 사용자의 빠른 연속 클릭을 하나의 액션으로 처리하여 불필요한 상태 업데이트나 작업 실행을 방지할 있습니다.

import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DebounceButtonExample(),
    );
  }
}

class DebounceButtonExample extends StatefulWidget {
  @override
  _DebounceButtonExampleState createState() => _DebounceButtonExampleState();
}

class _DebounceButtonExampleState extends State<DebounceButtonExample> {
  int _counter = 0;
  Timer? _debounce;

  void _incrementCounter() {
    if (_debounce?.isActive ?? false) _debounce!.cancel();
    _debounce = Timer(Duration(milliseconds: 500), () {
      setState(() {
        _counter++;
      });
      print('버튼이 눌렸습니다. 카운터: $_counter');
    });
  }

  @override
  void dispose() {
    _debounce?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Debounce Button Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '버튼을 누른 횟수:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

 

'Flutter > Skills' 카테고리의 다른 글

제네릭 <T> 를 사용할때, 착각하기 쉬운 경우  (0) 2024.11.26
Pagination  (0) 2024.11.05
Unit Test  (0) 2022.12.01
gskinner  (0) 2022.11.22
Dio + Retrofit + JsonSerializable 통한 서버 통신  (0) 2022.11.18