ng老狗的Vue 3 响应式系统源码分享

Ng老狗的Vue 3 响应式系统笔记

响应式库 @vue/reactivity 学习记录

背景

作为一个多年Angular应用的开发者,提到响应式,第一反应就是Rxjs。

相比于Promise的命令式编程分散的信号源, Rxjs的声明式响应数据流,在 用户事件多样且频繁的组件 中,能更优雅地处理异步数据流,往往 一个Observable就能完整的体现整个数据流 。

初见Vue 3的响应式,是一种既熟悉有新鲜的感觉。

我不再需要各种各样的Observable, Subject, BehaviorSubject 来表达数据流, 而是通过简单的ref和reactive就能创建响应式数据; 我也不需要去订阅和取消订阅数据流, Vue 3的响应式系统会 自动追踪依赖,并在数据变化时更新。

响应式亦有不同

诚然,Rxjs是一个强大的响应式编程库,适用于复杂的异步数据流处理, 而Vue 3的响应式系统更专注于UI层的数据绑定和状态管理, 在前端开发中,提供了极其便捷和高效的响应式编程体验。

Ps: Angular 16也引入了类似Vue 3的响应式系统,称为Signals。

基础概念

ref 和 reactive 对比 Observable

reactivity中最重要的无疑是 ref 和 reactive, 他们类似 Rxjs 中的 BehaviorSubject, 用于收集下游事件,并在更新时 广播 订阅者。

而 effect, computed 则完成了响应式数据流的蜕变, 自动的依赖收集和触发,彻底摆脱了手动订阅和取消订阅的繁琐。

以自动加法,举一个栗子:

typescript 复制代码
/************* @vue/reactivity ***************/
import { ref, reactive, computed, effect } from '@vue/reactivity';
const a = ref(1);
const b = ref(2);
// 响应式数据流
const sum = computed(() => a.value + b.value);
// 触发
a.value = 3;

console.log(sum.value); // 3 + 2 = 5

/************* Rxjs **************************/
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
const a$ = new BehaviorSubject(1);
const b$ = new BehaviorSubject(2);
const sum$ = combineLatest([a$, b$]).pipe(
  map(([a, b]) => a + b)
);

// 触发
a$.next(3);
// 特别的数据流只有在订阅时, 才会触发上游计算
sum$.subscribe(console.log); // 3 + 2 = 5

源码阅读

响应式系统的源码并不复杂,核心部分大约几百行代码。 主要包括以下几个模块:

  • 响应式数据创建:ref和reactive函数
  • 依赖收集和触发:effect函数
  • 计算属性:computed函数
  • 响应式数据的代理和拦截:Proxy对象
  • 响应式数据的只读和深度响应式:readonly和shallowReactive函数 ...等等

其中,依赖收集和触发是响应式系统的核心

一、依赖收集

  • 看懂 effect,就基本了解依赖收集的核心原理
typescript 复制代码
//  例如:
import { effect, ref } from '@vue/reactivity';
const count = ref(0);
effect(() => {
  console.log(`count is: ${count.value}`);
});

在这里如果把 .value 不要看作字段的取值,而是看成 .subscribe(currentEffect) 方法的调用,整个事情就变得很清晰了。

我只需要在 effect 的函数中执行时, 添加一个 currentEffect的全局变量 ,作为上下文, 每次 .subscribe() 的时候,把这个上下文添加到依赖列表中, 就很方便地完成了依赖收集。

computed 和 watch 也是类似的原理。

二、依赖触发

由于值更新的场景更灵活,依赖触发的设计也就更加复杂些。

Vue中按照 基本数据类型 和 对象类型 分别处理。参考GitHub

当然,非必要的场景下,Vue 也提供了 shallowReactive 和 shallowRef 来创建浅响应式对象。

三、计算属性

计算属性 computed 的实现也很巧妙。 它本质上是一个带有缓存的 effect。 当计算属性的依赖发生变化时,计算属性会重新计算值, 但只有在访问计算属性的值时才会触发重新计算。

这点和Rxjs中的 shareReplay 操作符有些类似。

四、总结

通过阅读 Vue 3 响应式系统的源码, 对响应式编程的有了新的理解。

核心的依赖清单通过数组管理、 相似的触发逻辑 和响应式数据流的理念,等等保留了发布订阅的稳定设计。

Vue 3 的响应式库,不拘泥于刻板的设计模式, 而是根据实际需求,灵活地设计了响应式数据的创建、依赖收集和触发机制, 从而实现了高效且易用的响应式编程体验。

Vue团队中Anthony Fu在这篇文章中对设计理念有更清晰的阐述,推荐一读。

相关推荐
大雄很用心2 天前
package.json 中的 dependencies 是否都会被下载?|Angular 回归笔记 02
angular.js
starrysky8102 天前
同一个 submit(),两条报错——ThreadPoolExecutor 在解释器退出时的竞态(附 4 组可复现实验)
angular.js
starrysky8102 天前
30 个 CPU 全在内核态,只回收了 32 个页——per-memcg 内存回收的 mmap_lock 活锁
angular.js
starrysky81016 天前
一个 NameError 掩盖了真根因:IPython 补全在 Django shell 里二次崩溃——从 jedi 版本错位查到 CPython LOAD_GLOBAL
angular.js
starrysky81016 天前
SQL 注入 + msgpack 反序列化 = RCE:LangGraph Checkpointer 三洞连爆——Agent 的记忆层就是攻击面
angular.js
starrysky81016 天前
NVD 9.8、官方 7.5:Redis TLS use-after-free 的评分之争——tlsProcessPendingData 悬指针踩空全解析
angular.js
巴勒个啦22 天前
2026 年 CSS 选型真相:我用 Tailwind v4 + 原生新特性重构了一个组件库
前端·angular.js
starrysky81024 天前
服务全绿、队列却 15 天没人消费?redis-py 8.0 默认切 RESP3 的阻塞命令坑
angular.js
starrysky81024 天前
写个管道就被 BrokenPipeError 打爆?先看 CPython 把 SIGPIPE 藏哪了
angular.js
starrysky8101 个月前
画像空不是故障:Honcho peer card 观察者视角源码拆解——1956 条结论为何换不来一张卡
angular.js