Effect 与批处理
基础 Effect
dart
final count = signal(context, 0);
final stop = effect(context, () {
debugPrint('count = ${count()}');
});
// 手动停止
stop();清理回调
onEffectCleanup 用于重新执行前清理,onEffectDispose 用于最终清理。
dart
effect(context, () {
onEffectCleanup(() => debugPrint('cleanup before re-run'));
onEffectDispose(() => debugPrint('dispose'));
});Widget 生命周期钩子
这些钩子用于把一次性的副作用和组件生命周期绑定在一起, 底层基于记忆化与 Element 跟踪。
何时使用
- 首帧结束后展示一次提示(toast/snackbar)。
- 启动一次性任务,并在组件移除时取消/释放。
- 监听器的生命周期需要与组件一致。
不适用场景
- 需要响应式重跑:用
effect。 - 需要清理
effect:用onEffectDispose。 - 需要每次 build 运行:直接放在
build。
行为说明
onMounted:首帧结束后执行一次。onUnmounted:组件从树中移除时执行一次(下一帧触发)。- 卸载再挂载会再次触发。
常见坑
- 必须在
build(或Builder)里调用,避免跨异步调用。 - 每次 build 的调用顺序必须一致且不可条件化。
onUnmounted在下一帧触发,测试中需要pump()。
示例
dart
Builder(
builder: (context) {
onMounted(context, () {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('Mounted')));
});
final timer = Timer(const Duration(seconds: 5), () {
debugPrint('tick');
});
onUnmounted(context, timer.cancel);
return const SizedBox();
},
);EffectScope
dart
final scope = effectScope(context, () {
effect(context, () => debugPrint('A'));
effect(context, () => debugPrint('B'));
});
scope(); // 释放范围内所有 effect批处理更新
dart
batch(() {
a.set(a() + 1);
b.set(b() + 1);
});Flutter 示例(来自 example 应用)
dart
class EffectBatchSection extends StatelessWidget {
const EffectBatchSection({super.key});
@override
Widget build(BuildContext context) {
final a = signal<int>(context, 1);
final b = signal<int>(context, 2);
final sum = computed<int>(context, (_) => a() + b());
final effectRuns = signal<int>(context, 0);
effect(context, () {
sum();
final current = untrack(() => effectRuns());
effectRuns.set(current + 1);
});
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('a: ${a()} b: ${b()} sum (computed): ${sum()}'),
Text('effect runs: ${effectRuns()}'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: [
ElevatedButton(
onPressed: () => a.set(a() + 1),
child: const Text('Increment A'),
),
ElevatedButton(
onPressed: () => b.set(b() + 1),
child: const Text('Increment B'),
),
OutlinedButton(
onPressed: () {
batch(() {
a.set(a() + 1);
b.set(b() + 1);
});
},
child: const Text('Batch +1 both'),
),
],
),
],
);
}
}