概述
Beacon 是一个 Dart 和 Flutter 的响应式原语(signal)和简单的状态管理解决方案。
Flutter Web 演示(源代码):https://flutter-beacon.surge.sh/ 所有示例:https://github.com/jinyus/dart_beacon/examples
![]()
安装
dart pub add state_beacon
用法
import 'package:state_beacon/state_beacon.dart';
创建一个 beacon
import 'package:flutter/material.dart';
import 'package:flutter_beacon/flutter_beacon.dart';
final counter = Beacon.writable(0);
// The future will be recomputed whenever the counter changes
final derivedFutureCounter = Beacon.derivedFuture(() async {
final count = counter.value;
return await counterFuture(count);
});
Future<String> counterFuture(int count) async {
await Future.delayed(Duration(seconds: count));
return '$count second has passed.';
}
在 widget 中观察它
class Counter extends StatelessWidget {
const Counter({super.key});
@override
Widget build(BuildContext context) {
return Text(
counter.watch(context).toString(),
style: Theme.of(context).textTheme.headlineMedium!,
);
}
}
class FutureCounter extends StatelessWidget {
const FutureCounter({super.key});
@override
Widget build(BuildContext context) {
return switch (derivedFutureCounter.watch(context)) {
AsyncData<String>(value: final v) => Text(v),
AsyncError(error: final e) => Text('$e'),
AsyncLoading() => const CircularProgressIndicator(),
};
}
}
功能
- Beacon.writable:读取和写入值。
- Beacon.readable:只读值。
- Beacon.createEffect:响应 beacon 值的变化。
- Beacon.doBatchUpdate:将多个更新批处理为单个通知。
- Beacon.debounced:在指定的持续时间内对值更改进行去抖动。
- Beacon.throttled:根据持续时间限制值更改的速率。
- Beacon.filtered:根据过滤条件更新值。
- Beacon.timestamped:为每个值更新附加时间戳。
- Beacon.undoRedo:撤销和重做值更改。
- Beacon.bufferedCount:根据整数限制创建值缓冲区/列表。
- Beacon.bufferedTime:根据时间限制创建值缓冲区/列表。
- Beacon.stream:从 Dart 流创建 beacon。
- Beacon.future:从 Future 初始化 beacon。
- Beacon.derived:根据其他 beacon 反应式地计算值。
- Beacon.derivedFuture:异步计算带有状态跟踪的值。
- Beacon.list:反应式地管理列表。
Beacon.writable
从一个可读写的值创建 WritableBeacon。
final counter = Beacon.writable(0);
counter.value = 10;
print(counter.value); // 10
Beacon.lazyWritable
类似于 Beacon.writable,但行为像一个 late 变量。它必须在读取之前设置。
final counter = Beacon.lazyWritable();
print(counter.value); // throws UninitializeLazyReadException()
counter.value = 10;
print(counter.value); // 10
Beacon.readable
从一个值创建不可变的 ReadableBeacon。这对于将 beacon 的值公开给消费者而又不允许他们修改它很有用。
final counter = Beacon.readable(10);
counter.value = 10; // Compilation error
Beacon.createEffect
根据提供的函数创建效果。提供的函数将在其任何依赖项更改时被调用。
final age = Beacon.writable(15);
Beacon.createEffect(() {
if (age.value >= 18) {
print("You can vote!");
} else {
print("You can't vote yet");
}
});
// Outputs: "You can't vote yet"
age.value = 20; // Outputs: "You can vote!"
Beacon.doBatchUpdate
执行批量更新,允许将多个更新批处理为单个更新。这可以通过减少更新通知的数量来优化性能。
final age = Beacon.writable<int>(10);
var callCount = 0;
age.subscribe((_) => callCount++);
Beacon.doBatchUpdate(() {
age.value = 15;
age.value = 16;
age.value = 20;
age.value = 23;
});
expect(callCount, equals(1)); // There were 4 updates, but only 1 notification
Beacon.derived
创建一个 DerivedBeacon,其值由计算函数派生。每当其任何依赖项发生更改时,此 beacon 都会重新计算其值。
示例
final age = Beacon.writable<int>(18);
final canDrink = Beacon.derived(() => age.value >= 21);
print(canDrink.value); // Outputs: false
age.value = 22;
print(canDrink.value); // Outputs: true
Beacon.derivedFuture
创建一个 DerivedBeacon,其值由异步计算派生。每当其任何依赖项发生更改时,此 beacon 都会重新计算其值。结果被包装在 AsyncValue 中,它可以处于三种状态之一:加载、数据或错误。
如果 manualStart 为 true(默认值:false),则在调用 [start()] 之前,Future 不会执行。
如果 cancelRunning 为 true(默认值:true),则如果在当前执行完成之前触发了另一个执行,则当前执行的结果将被丢弃。
示例
final counter = Beacon.writable(0);
// The future will be recomputed whenever the counter changes
final derivedFutureCounter = Beacon.derivedFuture(() async {
final count = counter.value;
await Future.delayed(Duration(seconds: count));
return '$count second has passed.';
});
class FutureCounter extends StatelessWidget {
const FutureCounter({super.key});
@override
Widget build(BuildContext context) {
return switch (derivedFutureCounter.watch(context)) {
AsyncData<String>(value: final v) => Text(v),
AsyncError(error: final e) => Text('$e'),
AsyncLoading() => const CircularProgressIndicator(),
};
}
}
Beacon.debounced
创建一个带有初始值和去抖动持续时间的 DebouncedBeacon。此 beacon 根据持续时间延迟其值的更新。
var myBeacon = Beacon.debounced(10, duration: Duration(seconds: 1));
myBeacon.value = 20; // Update is debounced
print(myBeacon.value); // Outputs: 10
await Future.delayed(Duration(seconds: 1));
print(myBeacon.value); // Outputs: 20
Beacon.throttled
创建一个带有初始值和节流持续时间的 ThrottledBeacon。此 beacon 根据持续时间限制其值的更新速率。比节流持续时间更新得更快的值将被忽略。
const k10ms = Duration(milliseconds: 10);
var beacon = Beacon.throttled(10, duration: k10ms);
beacon.set(20);
expect(beacon.value, equals(20)); // first update allowed
beacon.set(30);
expect(beacon.value, equals(20)); // too fast, update ignored
await Future.delayed(k10ms * 1.1);
beacon.set(30);
expect(beacon.value, equals(30)); // throttle time passed, update allowed
Beacon.filterd
创建一个带有初始值和过滤函数的 FilteredBeacon。此 beacon 仅在其通过过滤条件时才更新其值。过滤函数接收上一个值和新值作为参数。还可以使用 setFilter 方法更改过滤函数。
简单示例
var pageNum = Beacon.filtered(10, (prev, next) => next > 0); // only positive values are allowed
pageNum.value = 20; // update is allowed
pageNum.value = -5; // update is ignored
当过滤函数依赖于另一个 beacon 时
var pageNum = Beacon.filtered(1); // we will set the filter function later
final posts = Beacon.derivedFuture(() async {Repository.getPosts(pageNum.value);});
pageNum.setFilter((prev, next) => posts.value is! AsyncLoading); // can't change pageNum while loading
Beacon.timestamped
创建一个带有初始值的 TimestampBeacon。此 beacon 会为每个值更新附加时间戳。
var myBeacon = Beacon.timestamped(10);
print(myBeacon.value); // Outputs: (value: 10, timestamp: __CURRENT_TIME__)
Beacon.undoRedo
创建一个带有初始值和可选历史记录限制的 UndoRedoBeacon。此 beacon 允许撤销和重做其值的更改。
var undoRedoBeacon = UndoRedoBeacon<int>(0, historyLimit: 10);
undoRedoBeacon.value = 10;
undoRedoBeacon.value = 20;
undoRedoBeacon.undo(); // Reverts to 10
undoRedoBeacon.redo(); // Goes back to 20
Beacon.bufferedCount
创建一个 BufferedCountBeacon,用于收集和缓冲指定数量的值。一旦达到计数阈值,beacon 的值就会更新为收集值的列表,并且缓冲区会被重置。
此 beacon 在需要聚合一定数量的值然后一起处理的场景中很有用。
var countBeacon = Beacon.bufferedCount<int>(3);
countBeacon.subscribe((values) {
print(values);
});
countBeacon.value = 1;
countBeacon.value = 2;
countBeacon.value = 3; // Triggers update and prints [1, 2, 3]
Beacon.bufferedTime
创建一个 BufferedTimeBeacon,用于在指定的时间段内收集值。一旦时间段过去,beacon 的值就会更新为收集值的列表,并且缓冲区会被重置。
var timeBeacon = Beacon.bufferedTime<int>(duration: Duration(seconds: 5));
timeBeacon.subscribe((values) {
print(values);
});
timeBeacon.value = 1;
timeBeacon.value = 2;
// After 5 seconds, it will output [1, 2]
Beacon.stream
从给定的流创建一个 StreamBeacon。此 beacon 根据流发出的值更新其值。这可以包装在 Throttled 或 Filtered beacon 中以控制更新速率。
var myStream = Stream.periodic(Duration(seconds: 1), (i) => i);
var myBeacon = Beacon.stream(myStream);
myBeacon.subscribe((value) {
print(value); // Outputs the stream's emitted values
});
Beacon.future
创建一个 FutureBeacon,它根据 Future 初始化其值。可以通过调用 reset 方法来刷新它。
如果 manualStart 为 true,则在调用 [start()] 之前,Future 不会执行。
var myBeacon = Beacon.future(() async {
return await Future.delayed(Duration(seconds: 1), () => 'Hello');
});
myBeacon.subscribe((value) {
print(value); // Outputs 'Hello' after 1 second
});
Beacon.list
使用初始列表值创建 ListBeacon。此 beacon 管理一个项目列表,允许对列表进行反应式更新和操作。
var nums = Beacon.list<int>([1, 2, 3]);
Beacon.createEffect(() {
print(nums.value); // Outputs: [1, 2, 3]
});
nums.add(4); // Outputs: [1, 2, 3, 4]
nums.remove(2); // Outputs: [1, 3, 4]
myWritable.wrap(anyBeacon)
包装现有 beacon 并消耗其值
提供一个 (then) 函数来自定义如何处理发出的值。
var bufferBeacon = Beacon.bufferedCount<int>(10);
var count = Beacon.writable(5);
// Wrap the count beacon and provide a custom transformation.
bufferBeacon.wrap(count, then: (beacon, value) {
// Custom transformation: Add the value twice to the buffer.
beacon.add(value);
beacon.add(value);
});
print(bufferBeacon.buffer); // Outputs: [5, 5]
count.value = 10;
print(bufferBeacon.buffer); // Outputs: [5, 5, 10, 10]