Vue3响应式原理

2022.06.23
评论

原视频:Vue 3 Reactivity - Vue 3 Reactivity | Vue Mastery

本文为vue3响应式课程视频的笔记,请先具备以下技能后阅读:Vue3 WeakMap\Map\Set Proxy

一. 依赖收集触发的实现(track\trigger WeakMap\Map\Set)

依赖的雏形为一种三层结构,从内到外分为:根对象->根对象的属性->根对象的属性对应的事件集

// 收集列表 根对象-子属性-事件集
const targetMap = new WeakMap()
// 伪触发事件
const effect = () => {}
 
/**
 * 收集器
 * @param {Object} target 根对象
 * @param {String} key 子属性名称
 */
function track(target, key) {
	// 判断并 创建/获取 属性对应的事件集
	let depsMap = targetMap.get(target)
	if (!depsMap) {
		targetMap.set(target, depsMap = new Map())
	}
	let dep = depsMap.get(key)
	if (!dep) {
		depsMap.set(key, dep = new Set())
	}
 
	// 将事件放入 属性对应的事件集
	dep.add(effect)
}
 
/**
 * 触发器
 */
function trigger(target, key) {
	const depsMap = targetMap.get(target)
	if (!depsMap) return;
	const dep = depsMap.get(key)
	if (dep) {
		dep.forEach(effect => effect())
	}
}

二. 对象代理reactive(proxy/reflect)

/**
 * @param {*} target 被代理对象
 */
function reactive(target) {
	// 使用Proxy代理对象,进行get/set拦截
	return new Proxy(target, {
		get(target, k, rec) {
			// 收集
			track(target, k)
			// 执行get原有的默认操作
			return Reflect.get(target, k, rec)
		},
		set(target, k, v, rec) {
            const oldV = target[k]
			// 执行set原有的默认操作(需要优先执行,不然触发器无法获取最新的数据)
			const res = Reflect.set(target, k, v, rec)
			
			if (oldV !== v) {
				trigger(target, k)
			}
			
			return res
		}
	})
}

实验一下

const aa = reactive({
	p: 5,
	q: 2
})
let total = 0
const effect = () => {
	console.log('re', aa);
	total = aa.p * aa.q
}
effect()
console.log(total) // 10
aa.q = 5
console.log(total) // 25

三. 事件收集与值代理ref

事件收集

之前的代码中,代理属性的事件effect为伪代码,会导致只有一个对应的effect,接下来我们将其替换为正规格式

首先,我们修改 const effect = () => {} 方法,将其实现为一个属性对应多个effect

// 当活动中的effect事件
let activeEffect = null
 
/**
 * 事件收集器
 * @param {*} eff 事件
 */
function effect(eff) {
	activeEffect = eff
	// 由于eff内部会用到代理的对象,所以执行时会触发track收集eff
	activeEffect()
	// 代理结束,清空
	activeEffect = null
}

接下来,我们修改track

function track(target, key) {
	if (activeEffect) { // 增加判断,当前是否存在需要收集的对应事件 
		let depsMap = targetMap.get(target)
		if (!depsMap) {
			targetMap.set(target, depsMap = new Map())
		}
		let dep = depsMap.get(key)
		if (!dep) {
			depsMap.set(key, dep = new Set())
		}
 
		dep.add(activeEffect) // 收集活动中的事件
	}
}

值代理

/**
 * @param {*} v 被代理的值
 */
function ref(v) {
	const r = {
		set value(newV) {
			if (newV !== v) {
				(v = newV)
				trigger(r, 'value')
			}
		},
		get value() {
			track(r, 'value')
			return v
		}
	}
	return r
}

实验一下

const aa = reactive({
	p: 5,
	q: 2
})
let total = 0
let saleTotal = ref(0)
 
effect(() => {
	saleTotal.value = aa.p * 0.8
})
effect(() => {
	total = saleTotal.value * aa.q
})
 
console.log(total, saleTotal.value) // 8 4
aa.q = 5
console.log(total, saleTotal.value) // 20 4
aa.p = 10
console.log(total, saleTotal.value) // 40 8

四. 计算属性computed

function computed(getter) {
	const res = ref()
	effect(() => res.value = getter())
	return res
}

实验一下

const aa = reactive({
	p: 5,
	q: 2
})
const saleTotal = computed(() => aa.p * 0.8)
const total = computed(() => saleTotal.value * aa.q)
 
console.log(total.value, saleTotal.value) // 8 4
aa.q = 5
console.log(total.value, saleTotal.value) // 20 4
aa.p = 10
console.log(total.value, saleTotal.value) // 40 8

完整的代码

// 收集列表 根对象-子属性-事件集
const targetMap = new WeakMap()
// 当前触发的effect事件
let activeEffect = null
 
/**
 * 事件收集器
 * @param {*} eff 事件
 */
function effect(eff) {
	activeEffect = eff
	// 由于eff内部会用到代理的对象,所以执行时会触发track收集eff
	activeEffect()
	// 代理结束,清空
	activeEffect = null
}
 
/**
 * 收集器
 * @param {Object} target 根对象
 * @param {String} key 子属性名称
 */
function track(target, key) {
	if (activeEffect) {
		// 判断并 创建/获取 属性对应的事件集
		let depsMap = targetMap.get(target)
		if (!depsMap) {
			targetMap.set(target, depsMap = new Map())
		}
		let dep = depsMap.get(key)
		if (!dep) {
			depsMap.set(key, dep = new Set())
		}
 
		// 将事件放入 属性对应的事件集
		dep.add(activeEffect)
	}
}
 
/**
 * 触发器
 * @param {Object} target
 * @param {Object} key
 */
function trigger(target, key) {
	const depsMap = targetMap.get(target)
	// console.log(depsMap);
	if (!depsMap) return;
	const dep = depsMap.get(key)
	if (dep) {
		dep.forEach(effect => effect())
	}
}
 
/**
 * @param {*} target 
 */
function reactive(target) {
	// 使用Proxy代理对象,进行get/set拦截
	return new Proxy(target, {
		get(target, k, rec) {
			// 收集
			track(target, k)
			// 执行get原有的默认操作
			return Reflect.get(target, k, rec)
		},
		set(target, k, v, rec) {
			const oldV = target[k]
			// 执行set原有的默认操作(需要优先执行,不然触发器无法获取最新的数据)
			const res = Reflect.set(target, k, v, rec)
 
			if (oldV !== v) {
				trigger(target, k)
			}
 
			return res
		}
	})
}
 
/**
 * @param {*} v 被代理的值
 */
function ref(v) {
	const r = {
		set value(newV) {
			if (newV !== v) {
				(v = newV)
				trigger(r, 'value')
			}
		},
		get value() {
			track(r, 'value')
			return v
		}
	}
	return r
}
 
function computed(getter) {
	const res = ref()
	effect(() => res.value = getter())
	return res
}