[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() 找到这个属性的依赖列表,然后执行对应的副作用函数,触发更新逻辑。

相关推荐
2601_949809598 小时前
flutter_for_openharmony家庭相册app实战+我的Tab实现
java·javascript·flutter
Up九五小庞8 小时前
开源埋点分析平台 ClkLog 本地部署 + Web JS 埋点测试实战--九五小庞
前端·javascript·开源
摘星编程9 小时前
React Native + OpenHarmony:UniversalLink通用链接
javascript·react native·react.js
qq_177767379 小时前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos
烬头88219 小时前
React Native鸿蒙跨平台应用实现了onCategoryPress等核心函数,用于处理用户交互和状态更新,通过计算已支出和剩余预算
前端·javascript·react native·react.js·ecmascript·交互·harmonyos
程序员清洒11 小时前
Flutter for OpenHarmony:Text — 文本显示与样式控制
开发语言·javascript·flutter
雨季66611 小时前
Flutter 三端应用实战:OpenHarmony 简易“动态内边距调节器”交互模式深度解析
javascript·flutter·ui·交互·dart
天人合一peng11 小时前
Unity中button 和toggle监听事件函数有无参数
前端·unity·游戏引擎
会飞的战斗鸡12 小时前
JS中的链表(含leetcode例题)
javascript·leetcode·链表
方也_arkling12 小时前
别名路径联想提示。@/统一文件路径的配置
前端·javascript