[Flutter小试牛刀] 低配版signals,添加多层监听链

本文续接上篇文章: #Flutter小试牛刀 写一个低配版的signals

在上篇文章里,我们已经写了一个减配版的signals,但它仍然有很多缺陷:

  1. 节点的改变监听应该自己维护,当获取自己的值时可以重置监听链。
  2. 它不支持多层监听链,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.将全局节点重置成之前的缓存值

自此所有改造已经完成

相关推荐
暗夜行者之光15 分钟前
LangGraph + MCP 实战:构建"自然语言查数据"的 ChatBI Agent
前端·后端
一tiao咸鱼19 分钟前
还在手动海投?我做了个 AI 求职助手,自动筛选 BOSS 职位再精准投递
前端·后端·面试
尤水就下21 分钟前
🔥预热:在线文档的 block 架构是如何实现的(未完待续...)
前端·javascript·架构
hai_android40 分钟前
Android 嵌套滑动冲突:内部拦截 vs 外部拦截实战
android·android studio·android jetpack
全栈项目管理程序猿44 分钟前
ArcGIS JS 基础教程(29):CSVLayer 表格点位图层
前端·javascript
骑着蜗牛撵大象3271 小时前
豆包 Seed 驱动的记账智能体:从拍票抽取到月报生成的全链路落地记录
java·前端·javascript·结构化输出·豆包seed·多模态识别·记账系统
溪语流沙1 小时前
【Web全栈进阶】前端世界观:现代工具链与框架为什么存在
前端·javascript·vue.js
kyriewen2 小时前
我逐个测了 JSON.stringify 的 7 个坑:第 1 个就在丢数据
前端·javascript·程序员
积流成海2 小时前
用代码实现哈希计算器:Web Crypto API 实战
前端·算法·哈希算法·哈希函数·盘子工具站
data analyse 4562 小时前
服务端访问日志聚合 PV 与 UV 怎么实现?日志采集与口径设计
大数据·前端·数据分析