概述

Beacon 是一个 Dart 和 Flutter 的响应式原语(signal)和简单的状态管理解决方案。

Flutter Web 演示(源代码):https://flutter-beacon.surge.sh/ 所有示例:https://github.com/jinyus/dart_beacon/examples

demo

安装

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

从一个可读写的值创建 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 中,它可以处于三种状态之一:加载、数据或错误。

如果 manualStarttrue(默认值:false),则在调用 [start()] 之前,Future 不会执行。

如果 cancelRunningtrue(默认值: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 方法来刷新它。

如果 manualStarttrue,则在调用 [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]

GitHub

查看 Github