Angular信号用起来顺手,但一不注意就掉坑:明明调了.set(),视图纹丝不动;某条计算逻辑莫名其妙反复执行,根本控制不了频率;原本想用effect保持两个状态一致,最后却弄出了死循环。这些问题的根源出奇的一致——大家还在用老习惯去摆弄响应式管线,而没有顺着它的图工作。我把所有经验收敛成三条硬规矩,每一条都盯着同一个目标:让信号只做它承诺的事——细粒度更新、零浪费重算、可预测的数据流。
规矩一:把信号值当不可变数据用。很多人拿到信号的 current value 就忍不住 push 个元素、splice 一下或者直接给属性赋值,觉得这样更直接。但Angular的比对机制只看引用,不看内容。你改动了内部,外层引用却还是那个引用,那无论做了多少操作,Angular都检测不到变动,订阅者自然收不到任何通知。所以,我对团队的要求是:每一次对信号的写入,都必须产一个全新的值——不是建议,是铁律。
![]()
以会员列表为例,我绝不会写 this.members().push(newMember),而是老老实实用 update,把旧数组展开再追加:`this.members.update(members => [...members, newMember])`。删除成员也一样,用 filter 生成全新数组再 set 回去;对象操作同理,就算只改一个字段,也必须 `this.selectedMember.update(m => ({ ...m, status: 'active' }))`,绝不允许直接赋值。这带来的不是一句“代码更规范”的表面文章,而是从根源上保证了响应式图的每一次变动都有明确的通知路径。如果代码审查中只允许一票否决一条规则,我投给这一条。
规矩二:派生信号必须保持纯净。Angular 的计算属性API(computed())本质上是一个同步的派生工具,不是迷你任务调度器。它可能在脏检查周期内被多次评估,甚至在结果真正被消费之前就重复运行;上游信号一有风吹草动,它就可能重算一次,而你完全管不了它的执行频次。所以,任何不该出现在派生计算里的东西——HTTP请求、往其他信号里写数据、打印日志、读写 localStorage——都会以你无法控制的频率运行,要么变成性能黑洞,要么产生难排查的副作用。
我的处理方式很干脆:派生信号里只放纯的计算逻辑,比如根据所选定价层级和会员数算总价。像 `totalPrice = computed(() => calculateElasticPrice(tier, members))`——calculateElasticPrice 本身是个纯函数,没有 I/O,没有副作用。那埋点、统计分析这些有副作用的事放哪里?答案是在 effect 里处理,并且配合 untracked 使用,避免 effect 消费上游信号又把自己卷进重新执行的循环。比如在构造函数里这样写:
effect(() => {const price = this.totalPrice();untracked(() => this.analytics.track('price_calculated', { price }));effect 只管响应变化后的外部通信,派生信号只负责推导数据。职责一分开,数据流图立刻清爽多了。
规矩三:派生状态,别用 effect 去“同步”。有一种特别自然但极其危险的冲动:在一个 effect 里监听 A 信号,然后手动把它的值写进 B 信号,美其名曰“保持两个状态的一致性”。这等于自己动手搭了一条命令式的同步通路,而计算属性本来就可以免费帮你推导。效果是,多了一次变更检测,产生了更难追踪的数据流,而且一旦 effect 里读的、写的信号形成闭环,循环触发就不是理论上的恐怖故事,而是实实在在的线上事故。
构建会员入驻引导的步骤器时,这类需求特别典型。例如“能否继续下一步”的判断,我直接用一个派生信号搞定:
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.