核心概念
Signal
Signal 是响应式容器。读取用调用,写入用 .set(...)。
dart
final name = signal(context, 'Oref');
name(); // 读取
name.set('Oref v2'); // 写入Context 可为空(组件绑定 vs 全局)
所有核心 API 都接受 BuildContext?。传入 widget context 时,Oref 会在 组件生命周期内缓存并自动释放;传入 null 时,节点变成全局/独立 实例,需要你手动清理。
dart
// Widget build 内 -> 自动绑定 & 自动释放
final count = signal(context, 0);
effect(context, () => debugPrint('count: ${count()}'));
// Widget 外 -> 传 null 并手动释放
final globalCount = signal<int>(null, 0);
final stop = effect(null, () => debugPrint('global: ${globalCount()}'));
// later...
stop(); // 手动 dispose写入的常见写法
Signal 不是普通字段,更新需要先读再 .set(...):
dart
final count = signal(context, 0);
void increment() {
count.set(count() + 1);
}
void reset() {
count.set(0);
}Computed
Computed 会从信号派生并自动缓存。
dart
final count = signal(context, 2);
final squared = computed(context, (_) => count() * count());Writable Computed
可写 Computed 可以把写入映射回源信号。
dart
import 'dart:math' as math;
final count = signal<double>(context, 0);
final squared = writableComputed<double>(
context,
get: (_) => count() * count(),
set: (value) => count.set(math.sqrt(value)),
);Effect
Effect 会追踪依赖并在变化时重新执行。
dart
final count = signal(context, 0);
effect(context, () {
debugPrint('count = ${count()}');
});Batch 与 Untrack
批量更新:
dart
batch(() {
a.set(1);
b.set(2);
});不建立依赖的读取:
dart
final value = untrack(() => count());Flutter 示例(来自 example 应用)
dart
class CounterSection extends StatelessWidget {
const CounterSection({super.key});
@override
Widget build(BuildContext context) {
final count = signal<double>(context, 2);
final doubled = computed<double>(context, (_) => count() * 2);
final squared = writableComputed<double>(
context,
get: (_) => count() * count(),
set: (value) {
final safe = value < 0 ? 0.0 : value;
count.set(math.sqrt(safe));
},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('count: ${count().toStringAsFixed(1)}'),
Text('doubled (computed): ${doubled().toStringAsFixed(1)}'),
Text('squared (writable): ${squared().toStringAsFixed(1)}'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: [
ElevatedButton(
onPressed: () => count.set(count() + 1),
child: const Text('Increment'),
),
OutlinedButton(
onPressed: () => squared.set(81),
child: const Text('Set squared = 81'),
),
],
),
],
);
}
}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'),
),
],
),
],
);
}
}