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

相关推荐
吃饭睡觉打豆豆嘛33 分钟前
深入剖析 Promise 实现:从原理到手写完整实现
前端·javascript
前端端39 分钟前
claude code 原理分析
前端
GalaxyMeteor40 分钟前
Elpis 开发框架搭建第二期 - Webpack5 实现工程化建设
前端
Spider_Man40 分钟前
从 “不会迭代” 到 “面试加分”:JS 迭代器现场教学
前端·javascript·面试
我的写法有点潮40 分钟前
最全Scss语法,赶紧收藏起来吧
前端·css
小高00741 分钟前
🧙‍♂️ 老司机私藏清单:从“记事本”到“旗舰 IDE”,我只装了这 12 个插件
前端·javascript·vue.js
Mo_jon1 小时前
css 遮盖滚动条,鼠标移上显示
前端·css
EveryPossible1 小时前
终止异步操作
前端·javascript·vue.js
Stringzhua2 小时前
setup函数相关【3】
前端·javascript·vue.js
neon12042 小时前
解决Vue Canvas组件在高DPR屏幕上的绘制偏移和区域缩放问题
前端·javascript·vue.js·canva可画