Flutter 进阶:用 Function.apply 优雅的实现函数防抖 debounce

一、需求来源

项目中遇到问题,当在极短的时间内多次点击某个按钮时,会造成一些异常情况: 多次请求、多次进入等问题。常见的处理方法:

  • 加 loading,缺点是麻烦且动不动小菊花体验不好;
  • 用 防抖函数将原方法包一层,在防抖回调中调用原方法,缺点是代码层级加深;
  • 可能得第三种

以上常用方法虽然可以解决问题,但是代码一点不优雅,然后就开始寻找(苦苦思索)最优雅的实现方式。突然本周灵光一闪,它就自己出来了,记录一下分享给大家。

二、使用示例

1、 VoidCallback 类型函数示例:

只需要在定义的 VoidCallback 函数后调用 debounce,对历史代码零入侵;

dart 复制代码
OutlinedButton(
  onPressed: testVoidCallback.debounce,
  child: NText("testVoidCallback.debounce")
),

...

void testVoidCallback() {
  final index = IntExt.random(max: 1000);///生产随机数
  ddlog("testVoidCallback index: $index");
}

1.1、实现一个授权判断扩展方法:

现在app都需要游客模式,每录和没登录执行不同的方法;

dart 复制代码
OutlinedButton(
    onPressed: testVoidCallback.auth(
      onAuth: (){
        return false;
      },
      onUnauth: (){
        ddlog("OutlinedButton - onUnauth");
      }
    ),
    child: NText("testVoidCallback.auth")
),

2、 ValueChanged 函数类型函数示例:

因为目前dart 中没有提供函数的参数获取,只能使用箭头函数将原有的 val 暴露出来(dart api 限制),比 VoidCallback 优雅程度差一点;

dart 复制代码
Padding(
  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  child: NSearchTextField(
    backgroundColor: Colors.black12,
    onChanged: (val) => onChanged.debounce(value: val),
  ),
)

...

void onChanged(val){
  final index = IntExt.random(max: 1000);
  ddlog("onChanged: $val, index: $index");
}

三、源码

1、debounce 实现;

dart 复制代码
/// 搜索框防抖
class Debounce {
  Debounce({
    this.delay = const Duration(milliseconds: 500),
  });

  Duration delay;

  Timer? _timer;

  call(VoidCallback callback) {
    _timer?.cancel();
    _timer = Timer(delay, callback);
  }

  bool get isRunning => _timer?.isActive ?? false;

  void cancel() => _timer?.cancel();
}

2、函数扩展

dart 复制代码
final _debounce = Debounce();

extension FunctionExt on Function{
  /// 同 Function.apply
  static apply(
      Function function,
      List<dynamic>? positionalArguments,
      [Map<String, dynamic>? namedArguments]
  ) {
    final arguments = namedArguments?.map((key, value) => MapEntry(Symbol(key), value));
    return Function.apply(function, positionalArguments, arguments);
  }

  ///效果同 Function.apply
  applyNew({
    List<dynamic>? positionalArguments,
    Map<String, dynamic>? namedArguments,
  }) {
    final arguments = namedArguments?.map((key, value) => MapEntry(Symbol(key), value));
    return Function.apply(this, positionalArguments, arguments);
  }

  /// 防抖
  debounce({
    Duration duration = const Duration(milliseconds: 500),
    List<dynamic>? positionalArguments,
    Map<String, dynamic>? namedArguments,
  }){
    _debounce.delay = duration;
    return _debounce(() {
      applyNew(positionalArguments: positionalArguments, namedArguments: namedArguments);
    });
  }
}


extension VoidCallbackExt on VoidCallback {

  /// 延迟执行
  Future delayed({
    Duration duration = const Duration(milliseconds: 500),
  }) => Future.delayed(duration, this);

  /// 防抖
  void debounce({
    Duration duration = const Duration(milliseconds: 500),
  }){
    _debounce.delay = duration;
    _debounce(() => this());
  }

  /// 认证
  ///
  /// onAuth 返回认证状态
  /// onUnauth 未认证回调
  auth({
    required bool Function() onAuth,
    VoidCallback? onUnauth,
  }) {
    final hadAuth = onAuth();
    if (!hadAuth) {
      return (){
        onUnauth?.call();
      };
    }
    return this;
  }
}


extension ValueChangedExt<T> on ValueChanged<T> {

  /// 防抖
  debounce({
    required T value,
    Duration duration = const Duration(milliseconds: 500),
  }){
    _debounce.delay = duration;
    _debounce(() => this.call(value));
  }
}

四、总结

1、Dart 中函数也是对象,不同的函数的类型也是不同的,不同的函数类型实现不同的功能,大家要对函数有更深层次的理解。不能简单的认为函数就是函数就完了。
  • 什么场景使用回调函数而不是其他?
  • 什么场景使用内部函数而不是其他?
  • 什么场景使用函数指针而不是其他?
  • 还有哪些能提高我们开发效率的函数理解吗?

无论任何编程语言,你对函数理解越深,你的冗余代码就越少。

2、Function.apply 是 flutter 中函数动态调用方法,核心是提前声明一个函数,然后通过 Function.apply 可以传递位置参数(数组)和名称阐述(字典)
dart 复制代码
external static apply(Function function, List<dynamic>? positionalArguments,
    [Map<Symbol, dynamic>? namedArguments]);

参数介绍:

  • function 就是提前声明好的函数;
  • positionalArguments 就是位置参数,一般表现形式就是函数声明时没有用大括号括起来的参数;
  • namedArguments 就是名称参数,一般表现形式就是函数声明时用大括号括起来的参数;

官方示例:

dart 复制代码
void printWineDetails(int vintage, {String? country, String? name}) {
  print('Name: $name, Country: $country, Vintage: $vintage');
}

 void main() {
   Function.apply(
       printWineDetails, [2018], {#country: 'USA', #name: 'Dominus Estate'});
 }

注意点:

namedArguments 参数的key必须是 Symbol 类型;

dart 复制代码
final isEqual = Symbol("city") == #city;
ddlog("testVoidCallback isEqual: $isEqual");
// [log] 2024-04-21 12:33:04.265310 testVoidCallback isEqual: true
3、Function.apply 缺点是无法字符串(方法名)转方法,还达不到映射概念级别,只在动态调用函数级别,希望以后能支持。
4、其他

有些同学可能觉得 Function.apply 即麻烦还可能参数出错,但是高手从不怕自己的刀割破自己的手。当你脑中能装的复杂度越来越高时,其实也代表你处理问题的复杂度越来越高,能力越来越强。 Function.apply 有助于提升我们对于函数能力边界的理解。我们的目的不是漫无目的折腾,而是思维的扩展、能力和效率的提升。

github

相关推荐
爱泡脚的鸡腿3 分钟前
HTML CSS 第二次笔记
前端·css
灯火不休ᝰ19 分钟前
前端处理pdf文件流,展示pdf
前端·pdf
智践行20 分钟前
Trae开发实战之转盘小程序
前端·trae
最新资讯动态26 分钟前
DialogHub上线OpenHarmony开源社区,高效开发鸿蒙应用弹窗
前端
lvbb6636 分钟前
框架修改思路
前端·javascript·vue.js
树上有只程序猿38 分钟前
Java程序员需要掌握的技术
前端
从零开始学安卓41 分钟前
Kotlin(三) 协程
前端
阿镇吃橙子1 小时前
一些手写及业务场景处理问题汇总
前端·算法·面试
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(九)——FSP(First Screen Paint)像素级分析、RUM+合成监控、Lighthouse CI
前端·性能优化