[Vue]浅浅了解vue3响应式的基本原理

vue也用了这么多年了,从入门的2到现在的3,每次想表达它的响应式原理总是磕磕绊绊的。

所以我觉得有必要总结精炼一下,下次再被问到的话就能流畅的表达出来的。

事先说明下,以我半吊子的能力可能描述的不是很完善,参考就行。

2没有必要再说这么多了,直接vue3的setup响应式是怎么实现的吧。

我们知道在vue3里,通过reactive或者是ref我们可以创建响应式的数据。当我们触发set时,vue会自动去更新ui。

这其中的原理又是什么呢?

开始

当我们通过reactive创建一个响应式数据的时候,它会在内部通过proxy劫持get和set操作,即:

javascript 复制代码
function reactive(obj) {
	return new Proxy(obj, {
		get(target, key) {
			...
			return Reflect.get(target, key)
		},
		set(target, key, value) {
			...
			const result = Reflect.set(target, key, value);
			return result;
		}
	})
}

通过上面这个基础的reactive函数,我们应该能大概猜得到:

当触发get的时候,vue会在这里收集相关的依赖,记录是谁用了我。

而触发set时vue会根据get里收集的依赖去通知其异步调度批量更新ui;

下面来模拟reactive + effect(watchEffect):

声明一个变量用于记录正在运行的effect函数;

javascript 复制代码
let activeEffect = null;

声明effect函数,vue的watchEffect是传入一个回调函数,在这个回调函数里如果有响应式数据则会在每次数据变化时都会执行这个回调函数。

javascript 复制代码
function effect(cb) {
	activeEffect = cb;
	// 默认先执行一次并执行完后清空正在执行的effect回调
	cb();
	activeEffect = null;
}

声明一个Map对象用于收集依赖后执行响应的回调事件

javascript 复制代码
const bucket = new WeakMap();

依赖收集

javascript 复制代码
function track(target, key) {
	let depsMap = bucket.get(target);
  	if (!depsMap) {
    	depsMap = new Map();
    	bucket.set(target, depsMap);
  	}

  	let deps = depsMap.get(key);
  	if (!deps) {
    	deps = new Set();
    	depsMap.set(key, deps);
  	}

  	deps.add(activeEffect); // 记录当前 effect
}

触发通知

javascript 复制代码
function trigger(target, key) {
	const depsMap = bucket.get(target);
  	if (!depsMap) return;

  	const effects = depsMap.get(key);
  	if (!effects) return;

  	effects.forEach(fn => fn()); // 重新执行 effect 函数
}

修改上面的reactive函数,在get时触发依赖收集,而在set时触发通知。

javascript 复制代码
...
get(...) {
	if (activeEffect) {
      track(target, key); // 收集依赖
    }
    ...
}
set(...) {
	...
	trigger(target, key); // 触发更新
	...
}
...

此时我们使用reactive创建一个响应式数据并使用effect输出每次变化时的回调

javascript 复制代码
const state = reactive({ count: 0 });

effect(() => {
	console.log(`副作用执行:count =`, state.count);
})

state.count++;
state.count++;

此时会输出:

bash 复制代码
副作用执行:count = 0
副作用执行:count = 1
副作用执行:count = 2

我们来捋一下这期间发生了什么,通过ractive创建了一个响应式数据,此时通过proxy拦截get和set方法,并在get方法里进行依赖收集,记录到底是谁使用了我,建立依赖关系。

所以每个响应式对象的属性都维护着一组依赖它的副作用函数。

执行set时,它会根据target.key找到对应的依赖关系,是谁在使用我,然后重新执行这些副作用函数。

在effect里我们使用了state.count,触发了get方法,此时activeEffect就是当前的回调函数,所以直接进入到track函数里来并保存到WeakMap里,这个过程就是依赖收集。

而当我们state.count++即触发了set时,触发trigger进而根据 targe.key收集的依赖关系而触发对应的事件。

总结

当我们通过 reactive 创建一个响应式对象后,它内部通过 Proxy 劫持了 get 和 set 操作。

我们用 effect(fn) 注册一个副作用函数时,它会立即执行这个函数,并将 fn 存在 activeEffect 变量中。在这个执行过程中,如果访问了响应式对象的属性(触发 get),那么这个属性会通过 track() 把当前的 fn 注册为它的依赖。

以后当这个属性发生变化(触发 set),Vue 就会通过 trigger() 找到这个属性的依赖列表,然后执行对应的副作用函数,触发更新逻辑。

相关推荐
野槐3 小时前
前端项目优化(有了我,会发生...)
前端
幸运小圣3 小时前
PointerEvent 常用属性与事件整理【JavaScript】
开发语言·javascript·ecmascript
aa小小3 小时前
【无标题】
前端
计算机魔术师4 小时前
还在单线程跟 AI 聊天?Claude Code 并行多会话让你一个人干三个人的活
前端
志尊宝4 小时前
Vue3 零基础每日笔记(038):亲手封装 useDebounceFn 与 useThrottleFn——高频事件的流量阀
前端·javascript·vue·html·vue3
JavaGuide4 小时前
对标 MinIO!全新一代分布式文件系统正式发布
前端·后端
风骏时光牛马4 小时前
AI驱动自动化业务工作流搭建实践
前端
码云之上5 小时前
Skill 里的脚本终于能跑了,星悟接 CubeSandbox 的纪实
前端·人工智能·前端框架
IT_陈寒5 小时前
Vue computed属性这个坑,我居然踩了三次才爬出来
前端·人工智能·后端
烈风逍遥5 小时前
第三篇:组件化实践,SpTable 通用表格组件设计
前端·架构