【mini-vue3】【reactivity 模块】 reactive && effect && 依赖收集 && 触发依赖

119 阅读2分钟

reactive 最基础的功能,把一个对象封装成响应式对象。 核心的函数: Proxy(target, handler)
参数: target, 代理对象。 handler 在代理对象出现变动的时候,可以触发的操作函数, 如 get ,set ,del 等 思路流程图:

reactivity 依赖收集.png 采用TDD的操作,先写测试。

 it('happ path', () => {

 const original = { foo: 1} // 原对象

 const observed = reactive(original) // 响应式对象

 expect(observed).not.toBe(original)

 expect(observed.foo).toBe(1)

 expect(isReactive(observed)).toBe(true)

 expect(isReactive(original)).toBe(false)

 expect(isProxy(observed)).toBe(true)

  

 })

实现 reactive

reactive() -> 创建 createReactiveObject()



// 先定义map, 然后把 每一个对象内容以 key= target的形式存储
const targetMap = new Map()
const get = createGetter() 
const set = createSetter()
const basehandlers = { get , set }
 // 初始化的时候创建
function createReactiveObject() {
	const proxy  = new Proxy(taget, basehandlers)
}
// 初始化的时候 effect 包裹的函数会触发 get操作,
// 收集effect 中的 fn函数,以便set的时候调用。
function createGetter () {
	const res = Reflect.get(traget, key)
	// 收集依赖
	track(traget, key)
	return res
}
// 
function createSetter () {
	// 参数是 basehandlers
	return funciton set (target, key, value) {
		// **Reflect** 是一个内置的对象,它提供拦截 JavaScript 操作的方法。通过 Reflect 去把值设置。
		const res = Reflect.set(traget, key, value)
		// 触发update,依赖收集
		trigger(target, key)
		return res
	}
} 

触发依赖收集 :

function tarck (target, key) {
	// 取出target
	let depsMap = targetMap.get(target)
	if(!depsMap){
	 // 初始化的时候需要创建一个大的对象 ,存储全部dep,用map
	 depsMap = new Map()
	 targetMap.set(target, depsMap)
	}
	let dep = depsMap.get(key)
	if(!dep) {
	 // 初始化
	 // 不允许有重复的 key值对象,用了 set
	 dep = new Set()
	 depsMap.set(key, dep)
	}
	trackEffect(dep)
}

function trackEffect(dep) {
	 // 防止重复收集依赖.
	 if(dep.has(activeEffect)) return
	 dep.add(activeEffect)
}

触发update :

// update 触发。
export function trigger (target, key){
 let depsMap = targetMap.get(target)
 let dep = depsMap.get(key)
 triggerEffects(dep)
}
function triggerEffects (dep) {
	for(effect of dep) {
		effect.run()
	}
}

reactive 的响应实现完毕了, 现在实现effect函数。

effect 响应

测试代码:

it('happy path', () => {

 const user = reactive({

 age: 10,

 name: 'zhangsan'

 })

 let nextAge;

 let newName;

 effect(() => {

 nextAge = user.age + 1

 })

 effect(() => {

 newName = user.name + '2'

 })

 expect(nextAge).toBe(11)

 user.age++

 //update


 // expect(nextAge).toBe(12)

 // expect(newName).toBe('zhangsan2')

  

 })

获取到 fn : 创建一个类,定义一个全局变量去收集当前的 fn

// 全局变量
let activeEffect;
export class ReactiveEffect {
 private _fn: any
 deps = []
 constructor(fn, public ){
	 this._fn = fn
 }
 run() {
	 // 把传入的fn 赋值
	 activeEffect = this
	 // 执行fn
	 this._fn() 
 }
}

// 依赖收集
export function effect (fn, options:any = {}) {
 const _effect = new ReactiveEffect(fn, options.scheduler)
 _effect.run()
}

Q: 依赖收集的时候使用的 MAP 嵌套 SET A: target -> key -> dep

Q : 在响应式对象, reactive , ref 改变的时候,如何去触发 视图的改变和更新??为什么要实现 effect? mvvm ,数据操作视图, 当数据发生改变,视图应该也要发生改变。 我们再 render函数的时候, 已经把响应式对象,放入 render函数中。所以 当响应式对象发生改变的时候,回去调用 effect 中的 fn。 当响应式值发生改变的时候,触发 get、set ,触发 track ,触发 trigger -> 触发 triggerEffects , 然后触发 effect , effect.scheduler || effect.run 去更新dom。