Flutter中的防抖和节流

1,325 阅读4分钟

防抖与节流

前言

防抖与节流是前端性能优化中很重要的一环,防抖可以用来减少页面请求数量,节流则可以用于减少监听事件回调函数的触发次数。防抖与节流都是用来减少页面开销的方法。

虽然二者对性能提升有很大的帮助,但是**对用户体验而言,对点击事件做防抖或节流并不是一个好的解决方案,而更像是一种无奈之举,尽量不要使用。**我们可以对一些函数做一下防抖,但是尽量不要对按钮做,因为防抖的本质确实会给我们的用户交互带来体验上的“卡顿”。

防抖

防抖是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

举一个现实中的例子:商场里的感应扶梯,当没人的时候为了节能会降低运行速度。就是在检测到没人(触发事件)后 n 秒后触发减速(执行函数),如果n秒内又有人(又触发事件),则不减速(不执行函数)并重新计时。

aa4ba6a6f716442cb6e61d746ae2fc10.gif

在开发中主要用于在一定时间内不触发特定动作时再触发回调函数。比如输入框的自动搜索,当用户输入结束后再进行搜索。可以使用防抖避免用户每输入一个字符就进行一次搜索。

实现也很简单:

late Timer timer;

void debounce(Function function) {
  timer?.cancel();
  timer = Timer(
    Duration(milliseconds: 1000),
    () {
      ///do something
    },
  );
}

节流

节流是指在连续触发事件时,在 n 秒中只执行一次函数。它会稀释函数的执行频率。和防抖不同的是它会立即执行,然后在之后的n秒内不再响应。也就是规定了函数在单位事件内最多只能被触发一次。现实中常用的一个节流方法就是排队时限流。

代码实现如下:

void throttle() {
  if (timer != null) {
    return;
  }

  ///do something
  timer = Timer(
    Duration(milliseconds: 1000),
    () {
      timer = null;
    },
  );
}

工具类实现防抖节流

抽取工具类如下:

///默认时长
const _defaultDuration = Duration(milliseconds: 380);

enum FilterType {
  debounce,
  throttle,
}

typedef VoidFunction = void Function();
class EventFilter {
  static Map<String, Timer> _wrappers = {};

  ///防抖
  static VoidFunction debounce(String sign, function,
      {Duration duration = _defaultDuration}) {
    return () {
      execute(sign, function,
          duration: duration, filterType: FilterType.debounce);
    };
  }


  ///节流
  static VoidFunction throttle(String sign, function,
      {Duration duration = _defaultDuration}) {
    return () {
      execute(sign, function,
          duration: duration, filterType: FilterType.throttle);
    };
  }

  static void execute(String sign, function,
      {Duration duration = _defaultDuration,
      FilterType filterType = FilterType.debounce}) {
    switch (filterType) {
      case FilterType.debounce:
        _wrappers[sign]?.cancel();
        break;
      case FilterType.throttle:
        if (_wrappers.containsKey(sign)) {
          return;
        } else {
          function.call();
        }
        break;
    }

    _wrappers[sign] = Timer(
      duration,
      () {
        if (filterType == FilterType.debounce) {
          function.call();
        }
        _wrappers[sign]?.cancel();
        _wrappers.remove(sign);
      },
    );
  }

  ///在state的dispose方法里移除Timer
  static void remove(String sign) {
    if (_wrappers.containsKey(sign)) {
      _wrappers[sign]?.cancel();
      _wrappers.remove(sign);
    }
  }


  ///移除所有Timer
  static void clear() {
    _wrappers.forEach((key, value) {
      remove(key);
    });
    _wrappers.clear();
  }

  static void removeState(String hashString) {
    _wrappers.removeWhere((key, value) => key.startsWith(hashString));
  }
}

///State扩展类
///每个State所有的防抖和节流都带有同样的前缀
///方便管理和统一释放资源
extension EventFilterExtension on State {
  stateFilter(String sign, Function function,
      {Duration duration = _defaultDuration,
      FilterType filterType = FilterType.debounce}) {
    EventFilter.execute("${this.hashCode.toString()}$sign", function,
        duration: duration, filterType: filterType);
  }

  clearStateFilter() {
    EventFilter.removeState(this.hashCode.toString());
  }
}

使用方法如下:

 @override
  void dispose() {
    EventFilter.remove("tag");
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
    
      body: GestureDetector(
        onTap: EventFilter.throttle(
          "tag",
          () {
            print("------------");
          },
        ),
        child: Text("按钮"),
      ),
    );
  }

源码地址

总结

  • 防抖:n秒后执行;
  • 节流:n秒内最多执行一次。

在实际开发中,合理使用防抖和节流会减少性能负担,提升用户体验。但是要避免滥用,个人建议主要用在函数防抖上。

大多数人都喜欢去做点击事件的防抖。我个人是非常不推荐去给点击事件做防抖的。因为用户在大多数情况下是不太会无聊到去疯狂点击按钮的。大多数情况下出现连续点击的情况并不是因为用户真的“手抖”,而是按钮响应不及时或者反馈不明显。此时如果粗暴的使用防抖,对用户的体验不仅没有任何提升,反而会加重不好的体验。

所以,个人认为:当你想要对一个按钮使用防抖时,一定要明白这是下下策。在使用之前,你一定要思考一下有没有更好的交互方案去解决问题,比如加上更显著的点击反馈效果,或者视情况加入过度、转场效果等。当然,如果能做更深入的优化,提升流畅度是最好的。

对用户体验而言,按钮的防抖并不是一个好的解决方案,而更像是一种无奈之举。当你想要对一个按钮使用防抖时,首先要思考溯源,发现问题的本质,优化才是第一选择。 为了提升性能对一些函数做防抖和节流是没问题的,但是尽量不要对按钮做,因为防抖本质上确实会给我们的用户交互带来体验上的“卡顿”——因为它确实会带来“无响应”。