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

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794487 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存