Flutter:使用mixin实现4种防抖模式

112 阅读2分钟

首先给出demo:

demo:
void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyPage(),
    );
  }
}
class MyPage extends StatefulWidget {
  @override
     _MyPageState createState() => _MyPageState();
  // _MyPageState2 createState() => _MyPageState2();
  // _MyPageState3 createState() => _MyPageState3();
}

模式1:

mixin DebounceMixin {
  bool _debounce = false;

  Future<void> debouncer(Future<void> Function() callback) async {
    if (_debounce) return;
    _debounce = true;
    try {
      await callback();
    } finally {
      _debounce = false;
    }
  }
}

= demo ====================================================================

class _MyPageState extends State<MyPage> with DebounceMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Mixin Example")),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 执行异步操作
            await debouncer(() async {
              // 模拟一个耗时操作
              await Future.delayed(const Duration(seconds: 2));
              // 完成后操作
              print("===mixin demo===> 操作完成");
            });
          },
          child: const Text("开始加载"),
        ),
      ),
    );
  }
}

模式2(模式1的变式):

有时,可能需要对状态进行监听,可以使用 ValueNotifier ,同时配合 ValueListenableBuilder 实现。

mixin DebounceListenableMixin {
  final ValueNotifier<bool> debounce = ValueNotifier(false);

  Future<void> debouncer(Future<void> Function() callback) async {
    if (debounce.value) return;
    debounce.value = true;
    try {
      await callback();
    } finally {
      debounce.value = false;
    }
  }
}

= demo ====================================================================

class _MyPageState2 extends State<MyPage> with DebounceListenableMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Mixin Example")),
      body: Center(
        child: ValueListenableBuilder<bool>(
          valueListenable: debounce,
          builder: (context, debounce, child) {
            print("===mixin demo===> DebounceListenableMixin $debounce");
            return ElevatedButton(
              onPressed: () async {
                // 执行异步操作
                await debouncer(() async {
                  // 模拟一个耗时操作
                  await Future.delayed(const Duration(seconds: 2));
                  // 完成后操作
                  print("===mixin demo===> 操作完成");
                });
              },
              child: const Text("开始加载"),
            );
          },
        ),
      ),
    );
  }

  @override
  void dispose() {
    // 注意避免 ValueNotifier 的内存泄漏
    debounce.dispose();
    super.dispose();
  }
}

模式3(模式2的变式):

当然,对有状态的组件,可以使用on关键字,直接在 State 上进行混入,并且这样带来了好处,可以方便在mixin中对 ValueNotifier 进行管理。

mixin DebounceListenableStateMixin<T extends StatefulWidget> on State<T> {
  final ValueNotifier<bool> debounce = ValueNotifier(false);

  Future<void> debouncer(Future<void> Function() callback) async {
    if (debounce.value) return;
    debounce.value = true;
    try {
      await callback();
    } finally {
      debounce.value = false;
    }
  }

  @override
  void dispose() {
    debounce.dispose();
    super.dispose();
  }
}

模式4:

如果是针对频率进行限制:

mixin ThrottleMixin {
  var _lastTime = 0;

  Future<void> throttler(
    Future<void> Function() callback, {
    int milliseconds = 1000,
  }) async {
    final now = DateTime.now().millisecondsSinceEpoch;
    if (now - _lastTime > milliseconds) {
      _lastTime = now;
      try {
        await callback();
      } catch (_) {}
    }
  }
}

= demo ====================================================================

class _MyPageState3 extends State<MyPage> with ThrottleMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Mixin Example")),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 执行异步操作
            await throttler(
              () async {
                // 模拟一个耗时操作
                await Future.delayed(const Duration(seconds: 2));
                // 完成后操作
                print("===mixin demo===> 操作完成");
              },
            );
          },
          child: const Text("开始加载"),
        ),
      ),
    );
  }
}

使用mixin解决方案的优点:

灵活、可复用:不同组件都能使用
无侵入性:不改变原组件的默认行为

模式选择?

适用于防止按钮连点:模式1-模式3
适用于限制点击频率:模式4

mixin原理:

在 Dart 中,mixin 是一种代码复用的机制,允许将一组方法和属性注入到多个类中,而不需要使用继承。 mixin 类似于多继承,但它避免了多继承的复杂性。

1、定义mixin:使用 mixin 关键字来定义一个 mixin,一个 mixin 可以包含方法、属性
mixin LoggingMixin {
  void log(String message) {
    print("===mixin demo===> $message");
  }
}
2、使用mixin:使用 with 关键字,一个类可以使用多个 mixin
class User with LoggingMixin {
  final String name;

  User({
    required this.name,
  });

  void hello() {
    log('Hello,$name');
  }
}
3、使用多个mixin:一个类可以使用多个 mixin,只需在 with 后面列出多个 mixin,用逗号分隔
mixin ValidationMixin {
  bool isValid(String? input) {
    return input?.isNotEmpty ?? false;
  }
}

class Admin with LoggingMixin, ValidationMixin {
  final String name;

  Admin({
    required this.name,
  });

  void check() {
    if (isValid(name)) {
      log('$name is valid');
    } else {
      log('$name is invalid');
    }
  }
}
4、使用on关键字限制mixin的范围:使用 on 关键字来限制 mixin 只能用于特定类的子类
//在这个例子中,SpecialLoggingMixin 只能用于 User 类或其子类。
mixin SpecialLoggingMixin on User {
  void specialLog(String message) {
    print("===mixin demo===> specialLog:$message");
  }
}

class SuperUser extends User with SpecialLoggingMixin {
  SuperUser({required super.name});
}
5、“多继承”中的同名方法:with 允许多个 mixin 组合,且 右侧的 mixin 会覆盖左侧的
class A {
  void say() {
    print("A");
  }
}

mixin B on A {
  @override
  void say() {
    print("B");
  }
}

mixin C {
  void say() {
    print("C");
  }
}

class D extends A with B, C {
  // 如果重写了,依然调用当前类的方法
  // 如果没有重写,调用 with 最右边的方法
  // @override
  // void say() {
  //   print("D");
  // }
}

void main() {
  final d = D();

  d.say(); // console print: C
}
6、class mixin
mixin class E {
  // 既能作为mixin,又能作为常规的类
  // 但是,不能有 extends、with、on,因为前两者mixin没有,后者常规的类没有
}
7、mixin的限制
不能有构造函数
不能继承类
8、如何选择mixin、extends、implements?
mixin 适合多个类共享相同功能,但不影响继承体系
extends 适合建立类层级关系
implements 适合定义接口,但需手动实现所有方法
9、mixin的本质

mixin 本质上是一个没有构造函数的类,它不能被直接实例化,编译器将方法和属性“复制”到使用它的类中。

mixin LoggingMixin {
  void log(String message) {
    print("===mixin demo===> $message");
  }
}
class User with LoggingMixin {
  final String name;

  User({
    required this.name,
  });

  void hello() {
    log('Hello,$name');
  }
}

在编译时,LoggingMixin 中的 log 方法会被“复制”到 User 类中,相当于 User 类直接包含了 log 方法的实现。 当多个 mixin 被应用到同一个类时,Dart 会按照 with 关键字的顺序,将 mixin 中的方法和属性线性化。 这意味着后面的 mixin 会覆盖前面的 mixin 中同名的方法或属性,以此来解决冲突问题。