Oref 核心 API
引用(ref()
)
接收一个内部值,返回一个响应式的、可更改的 Ref<T>
对象,这个对象只有一个指向其内部值的属性 .value
。
类型:
dartRef<T> ref<T>(T value);
dartRef<T> ref<T>(BuildContext context, T value);
返回类型:
dartabstract interface class Ref<T> { T value; }
详细信息
Ref<T>
对象是可更改的,也就是说我们可以使用.value
赋予新的值。它也是响应式的,即对所有读取了.value
的操作都将被追踪,并且赋值操作会触发与之相关的副作用。示例:
dartfinal count = ref(0); print(count.value); // 0 count.value = 1; print(count.value); // 1
派生(derived()
)
derived()
接收一个 getter 函数(类型:T Function()
),返回一个只读的响应式 Derived<T>
对象。该 Derived<T>
通过 .value
暴露 getter 函数的返回值。
- 类型:dart
Derived<T> derived<T>(T Function() getter);
dartDerived<T> derived<T>(BuildContext context, T Function() getter);
- 返回类型dart
abstract interface class Derived<T> extends Ref<T> { T get value; }
- 示例dart
final count = ref(1); final plusOne = derived(() => count.value + 1); print(plusOne.value); // 2 plusOne.value++; // 无效,Dart VM 下在 DevTools console 中输出警告
dartfinal count = ref(context, 1); final plusOne = derived(context, () => count.value + 1); print(plusOne.value); // 2 plusOne.value++; // 无效,Flutter dev 模式下在 DevTools console 中输出警告
带值的派生(derived.valuable()
)
有时候我们实现派生时,需要使用上一个值加入到计算中,那么就需要 derived.valuable
:
final count = ref(0);
final total = derived.valuable<int>(
(prev) => count.value + (prev ?? 0)
);
print(total.value); // 0
count.value = 10;
print(total.value); // 10
count.value = 20;
print(total.value); // 30
final count = ref(context, 0);
final total = derived.valuable<int>(
context,
(prev) => count.value + (prev ?? 0)
);
print(total.value); // 0
count.value = 10;
print(total.value); // 10
count.value = 20;
print(total.value); // 30
可写的派生(derived.writable()
)
可写的派生允许你实现类似逆转计算的功能,我们需要用到 derived.writable()
函数:
final count = ref(0);
final doubleCount = derived.writable<int>(
(_) => count.value * 2, // count 乘 2
(value) => count.value = value ~/ 2, // 逆转计算,反向操作 count
);
doubleCount.value = 10;
print(count.value); // 5
count.value = 10;
print(doubleCount.value); // 20
final count = ref(context, 0);
final doubleCount = derived.writable<int>(
context,
(_) => count.value * 2, // count 乘 2
(value) => count.value = value ~/ 2, // 逆转计算,反向操作 count
);
doubleCount.value = 10;
print(count.value); // 5
count.value = 10;
print(doubleCount.value); // 20
由此,我们可以直接在派生响应式之上直接实现可逆转的响应式数据操作。
响应式集合 v0.4+oref_flutter: v0.3+
一种保持类型不变的响应式状态的方法,与使用内部 .value
包装在内部对象中的 ref 不同。响应式集合不改变 数据本身类型而保持响应性。
IMPORTANT
只有 Map
、List
、Set
、Iterable
集合类型支持。
创建一个响应式集合,如果本身具有响应性那么保持不变原样返回。
类型
dartMap<K, V> reactiveMap<K, V>(Map<K, V> map); Set<E> reactiveSet<E>(Set<E> set); List<E> reactiveList<E>(List<E> list); Iterable<E> reactiveIterable<E>(Iterable<E> iterable);
dartMap<K, V> reactiveMap<K, V>(BuildContext context, Map<K, V> map); Set<E> reactiveSet<E>(BuildContext context, Set<E> set); List<E> reactiveList<E>(BuildContext context, List<E> list); Iterable<E> reactiveIterable<E>(BuildContext context, Iterable<E> iterable);
示例
dartfinal obj = reactiveMap({"count": 0}); obj["count"] += 1;
dartfinal obj = reactiveMap({"count": 0}); obj["count"] += 1;
响应式集合是一个自定义实现集合接口的封装,与普通集合类型一样。不同的是,Oref 能够收集和拦截响应式集合所有属性 的访问和修改。
WARNING
由于响应式集合具有不明显的响应性特征,很多时候可能会误导开发者。因此我们应该尽量使用 ref
来管理状态。
副作用(effect()
)
立即运行一个函数,同时响应式地追踪函数内所使用的响应式数据作为依赖,并在被追踪的依赖更改时重新执行函数:
- 类型dart
EffectRunner<T> effect<T>( T Function() runner, { void Function()? scheduler, void Function()? onStop, });
dartEffectRunner<T> effect<T>( BuildContext context, T Function() runner, { void Function()? scheduler, void Function()? onStop, });
- 返回类型dart
abstract interface class EffectRunner<T> { Effect<T> get effect; T call(); } abstract interface class Effect<T> { void stop(); void pause(); void resume(); }
点击"Effect<T> class API" 查看更多信息
- 详细信息
context
: Flutter Widget 的上下文。Flutterrunner
: 需要执行的副作用函数。scheduler
: 自定义副作用触发器onStop
: 当副作用被停止时执行。
- 示例:dart
final count = ref(0); effect(() => print(count.value)); // -> 打印 0 count.value++; // -> 打印 1
dartfinal count = ref(context, 0); effect(context, () => print(count.value)); // -> 打印 0 count.value++; // -> 打印 1
副作用清除
有时候,我们在重新运行副作用函数之前,运行另一个函数对之前的资源进行清理:
final tick = ref(0);
final duration = ref(const Duration(seconds: 1))
effect(() {
// 监听 duration.value 并创建一个内部 Timer。
final timer = Timer.periodic(duration.value, (timer) {
tick.value = timer.tick;
});
// 当 duration 更新之前,停止上一个 Timer。
onEffectCleanup(() {
if (timer.isActive) timer.cancel();
});
});
final tick = ref(context, 0);
final duration = ref(context, const Duration(seconds: 1))
effect(context, () {
// 监听 duration.value 并创建一个内部 Timer。
final timer = Timer.periodic(duration.value, (timer) {
tick.value = timer.tick;
});
// 当 duration 更新之前,停止上一个 Timer。
onEffectCleanup(() {
if (timer.isActive) timer.cancel();
});
});
终止副作用
当我们不希望副作用函数继续侦听响应式属性时,我们可以这样停止它:
final runner = effect(() => ...);
// 停止副作用对响应式属性的侦听。
runner.effect.stop();
final runner = effect(context, () => ...);
// 停止副作用对响应式属性的侦听。
runner.effect.stop();
暂停/恢复
有时候,我们希望暂停而不是终止侦听器:
final runner = effect(() => ...);
// 暂停
runner.effect.pause();
// 稍后恢复
runner.effect.resume();
final runner = effect(context, () => ...);
// 暂停
runner.effect.pause();
// 稍后恢复
runner.effect.resume();
侦听器(watch()
)
侦听一个或多个响应式数据源构造为 Record
,并在数据源变化时调用所给的回调函数。
watch()
类型签名dartWatchHandle watch<T extends Record>( T Function() compute, void Function(T value, T? oldValue) runner, { bool immediate = false, bool once = false, })
dartWatchHandle watch<T extends Record>( BuildContext context, T Function() compute, void Function(T value, T? oldValue) runner, { bool immediate = false, bool once = false, })
类型
dartextension type WatchHandle { void stop(); void pause(); void resume(); void call(); // 可调用重载符号,等同于 stop() }
详细信息
watch()
与 effect 行为一致,但存在一些功能差异- 采用计算函数的方式将多个值封装成
Record
- runner 同时提供新值和久值。
- 默认时懒监听的,即仅在侦听源发生变化时才执行回调函数。
immediate
: 在侦听器创建时立即触发回调。第一次调用时旧值是null
。once
: 回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
- 采用计算函数的方式将多个值封装成
示例
侦听一个
Ref<T>
:dartfinal count = ref(0); watch( () => (count.value), (value, prev) {...} );
dartfinal count = ref(context, 0); watch( context, () => (count.value), (value, prev) {...} );
侦听多个:
dartfinal count = ref(0); final plusOne = derived(() => count + 1); watch( () => (count.value, plusOne.value), (value, prev) {...} );
dartfinal count = ref(context, 0); final plusOne = derived(context, () => count + 1); watch( context, () => (count.value, plusOne.value), (value, prev) {...} );
停止侦听器
final stop = watch(...);
stop(); // 停止侦听器
暂停/恢复侦听器
final WatchHandle(:stop, :pause, :resume) = watch(...);
pause(); // 暂停侦听器
resume(); // 稍后回复侦听
stop(); // 停止
侦听器的副作用清理
在 watch()
中和在 Effect - 副作用清除 一样,都是使用 onEffectCleanup()
函数。
温馨提示
watch()
是基于 effect()
进行高度优化封装的。
可观测(obs()
)Flutteroref_flutter: 0.2+
obs()
允许你观测一个 Ref<T>
并获取它的值构造 Widget,当 ref 更新时,仅仅更新这一个 Widget 而不是重建当前 Widget 树的所有节点:
class Counter extends StatelessWidget {
const Counter({super.key});
@override
Widget build(BuildContext context) {
final count = ref(context, 0);
return TextButton(
onPressed: () => count.value++,
child: obs(count, (count) => Text('Count: ${count}')),
);
}
}
当 count
内部的值更新时,只会重建 Text
Widget而不会让整个 Counter
重建。
如果你更喜欢函数式编程,也许你会更喜欢这样的使用方法:
class Counter extends StatelessWidget {
const Counter({super.key});
@override
Widget build(BuildContext context) {
final count = ref(context, 0);
return TextButton(
onPressed: () => count.value++,
child: count.obs((count) => Text('Count: ${count}')),
);
}
}