一、需求来源
项目中遇到问题,当在极短的时间内多次点击某个按钮时,会造成一些异常情况: 多次请求、多次进入等问题。常见的处理方法:
- 加 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 有助于提升我们对于函数能力边界的理解。我们的目的不是漫无目的折腾,而是思维的扩展、能力和效率的提升。