🔥🔥🔥Vue3响应式原理实现与track和trigger依赖收集和触发依赖

291 阅读3分钟

前言

Vue的响应式系统是基于数据劫持加发布订阅者模式实现的,数据响应式就是建立响应式数据依赖的关系 (调用了响应式数据的操作之间的关系) vue2使用Object.defineProperty进行数据拦截,而Vue3使用Proxy进行数据拦截是es6中新加的api,比Object.defineProperty解除了很多限制,性能更加优秀

Proxy

利用Proxy代理对象进行数据劫持

Proxy的基本使用

const testobj= { count: 0 };
const proxy = new Proxy(testobj, {
get(target, key) {
console.log('触发get')
return Reflect.get(target, key)
},
set(target, key, value) {
console.log('触发set')
return Reflect.set(target, key, value)
},
});
console.log(proxy.count) // 触发get
proxy.count = 1 // 触发set

在Vue3中,会通过拦截 getset 操作来实现依赖收集和触发依赖。当我们访问响应式对象的属性时,会触发 get 操作,此时 Vue3 会将当前的依赖(effect)项添加到依赖列表中;当我们修改响应式对象的属性时,会触发 set 操作,此时 Vue3 会遍历依赖列表,调用每个依赖项的更新函数,对视图进行更新。

所以先定义依赖类,需要有数据结构来存储依赖,

effect副作用函数,这个函数在响应式数据发生变化时会被自动触发,从而执行相关的操作。可以完善effect副作用函数来控制响应式数据

// 定义的依赖类
class ReactiveEffect{
	private _fn_;
	constructor(fn){
		this._fn_ = fn
	}
	run(){
		activeEffect = this
		this._fn_()
	}
}
let activeEffect;
//effect副函数
//传入一个直接执行的函数
function effect(fn){
	const _effect = new ReactiveEffect(fn)
	_effect.run()
}

当访问响应式对象的属性时,会添加一个依赖项,并将当前的 activeEffect 类保存到一个 effects 集合中,而 effects 集合则以 target 对象为键,以保存在该对象上的依赖项的列表为值。这也就是track收集依赖

为什么用WeakMap做数据结构呢?weakMap重要在它的弱字,WeakMap 中的键是弱引用的,意味着,当键不再被其他地方引用时,垃圾回收器可以自动清除这些键对应的值。可以优化性能

const targetMap = new WeakMap()

//收集依赖

function track(target, key) {

let depsMap = targetMap.get(target)

if (!depsMap) {

depsMap = new Map()

targetMap.set(target, depsMap)

}

let dep = depsMap.get(key);

if (!dep) {

dep = new Set()

depsMap.set(key, dep)

}

dep.add(activeEffect)
}
//简化下代码
function track(target, key) {

const depsMap = targetMap.get(target) || new Map();

targetMap.set(target, depsMap);

const dep = depsMap.get(key) || new Set();

depsMap.set(key, dep);

dep.add(activeEffect);

}

trigger触发依赖是当响应式对象属性修改时,系统会遍历依赖项列表,逐个执行保存在其中的 activeEffect 类,代表了对应依赖项的操作。

function trigger(target,key) {

const depsMap = targetMap.get(target)

const deps = depsMap.get(key)

for(const effect of deps){

effect.run()

}
}

这下可以根据上述实现一波reactive实现

let activeEffect;
function isObject(raw) {
if (raw === null) {
return false
} else if (typeof raw === 'object' || typeof raw === 'function') {
return true
} else {
return false
}
}
function reactive(raw){
	if (!isObject(raw)) {
	console.warn(`${raw} not object`)
	return raw
	}
	return new Proxy(raw,{
	get(target, key) {
		const res =Reflect.get(target, key)
		track(target,key)
		return res
	},
	set(target,key,value){
		const res = Reflect.set(target, key, value)
		trigger(target,key,value)
		return res //这是个布尔值
	}		
	})
}
//实现依赖类和effect函数
class ReactiveEffect{

_fn_;

constructor(fn){

this._fn_ = fn

}

run(){

activeEffect = this;

this._fn_();

}

}

function effect(fn){

const _effect = new ReactiveEffect(fn)

_effect.run()

}
const targetMap = new WeakMap()

//收集依赖
function track(target, key) {

let depsMap = targetMap.get(target)

if (!depsMap) {

depsMap = new Map()

targetMap.set(target, depsMap)

}

let dep = depsMap.get(key);

if (!dep) {

dep = new Set()

depsMap.set(key, dep)

}

dep.add(activeEffect)

}

//触发依赖
function trigger(target,key) {

const depsMap = targetMap.get(target)

const deps = depsMap.get(key)

for(const effect of deps){

effect.run()
}
}