本文续接上篇文章: #[Flutter小试牛刀] 写一个低配版的signals
在上篇文章里,我们已经写了一个减配版的signals,但它仍然有很多缺陷:
- 节点的改变监听应该自己维护,当获取自己的值时可以重置监听链。
- 它不支持多层监听链,computed里面只能引用use,用起来就很不方便。
我们先分析一下运用signals的代码:
dart
bool flag = true;
final a = signal(0);
final b = signal(1);
final c = computed(() => a.value + b.value);
final d = computed((){
if(flag) return c.value +b.value;
return b.value;
);
final dispose = effect((){ print("d is ${d.value}"); });
expect(c.value, 1);
expect(d.value, 2);
a.value = 2;
expect(c.value, 3);
expect(d.value, 4);
flag = false;
expect(c.value, 3);
expect(d.value, 1);
dispose();
从上面代码中可以看出 d监听了c值的改变,而c值也是算出来的,这就是需要我们的框架支持多层监听链。 我们还可以看到 flag的值决定了d变量的监听对象,如果flag = true,那么d需要监听c和b的值改变,如果flag = false,则只需要监听b的值改变。这就需要我们在每次调用d.value都需要重新建立它的监听体系。
为了解决问题1和问题2,我们对代码进行以下改造:
dart
abstract mixin class Node {
final Set<Node> children = {};
//将自己的值改变绑定到节点
void _subscribeToNode(Node node) {
if (children.contains(node)) {
return;
}
addListener(node.notifyListeners);
node.children.add(this);
}
//取消将自己的值改变绑定到节点
void unSubscribeToNode(Node node) {
if (children.contains(node)) {
node.removeListener(notifyListeners);
children.remove(node);
}
}
//清除所有监听
void _unsubscribe() {
for (var child in children) {
child.removeListener(notifyListeners);
}
children.clear();
}
void addListener(void Function() listener);
void removeListener(void Function() listener);
void dispose();
void notifyListeners();
}
class UseCompute<T> with Node {
DisposeFn<T>? onDispose;
ChangeFn<T>? onChange;
final CreateFn<T> onCreate;
final Set<void Function()> _listeners = {};
T? _value;
UseCompute(this.onCreate);
T? get value {
//如果全局节点不为空将自己的改变绑定到全局节点。
if (_currentNode != null) {
_subscribeToNode(_currentNode!);
}
//清除之前的监听节点列表。
_unsubscribe();
//缓存全局节点
var oldCurrentNode = _currentNode;
try {
//将自己设置成全局节点
_currentNode = this;
//重新建立监听节点列表。
var newValue = onCreate.call();
//如果值有改变则回调
if (_value != newValue) {
onChange?.call(newValue);
}
_value = newValue;
return _value;
} catch (error) {
rethrow;
} finally {
//将全局节点重置成之前的缓存值
_currentNode = oldCurrentNode;
}
}
//不添加监听获取值
T? peek() {
return _value;
}
@override
void addListener(void Function() listener) {
_listeners.add(listener);
}
@override
void removeListener(void Function() listener) {
_listeners.remove(listener);
}
@override
void dispose() {
_listeners.clear();
_unsubscribe();
onDispose?.call(_value);
_value = null;
}
@override
void notifyListeners() {
for (var listener in _listeners) {
listener.call();
}
}
class UseCreate<T> extends UseCompute<T> {
UseCreate(super.onCreate);
@override
T? get value {
//如果全局节点不为空则将自己的改变绑定到全局节点。
if (_currentNode != null) {
_subscribeToNode(_currentNode!);
}
//如果值为空则尝试初始化它
_value ??= onCreate.call();
return _value;
}
set value(T? newValue) {
if (_value == newValue) {
return;
}
_value = newValue;
notifyListeners();
onChange?.call(_value);
}
}
}
整个改造,UseCompute的 get value方法是核心。他们核心步骤是:
1.如果全局节点不为空则自己的改变事件绑定到全局节点。 2.清除之前的监听节点列表。 3.缓存全局节点,将自己设置成全局节点。 4.重新建立监听节点列表。 5.将全局节点重置成之前的缓存值
自此所有改造已经完成