今日初探两个函数
- reactive
- effect
reactive
用Proxy创建代理式对象,在get和set里面做一些相关操作,比如get里面取的值是对象时,将取出的对象继续代理,如: `
get(target, prop) {
const result = Reflect.get(target, prop)
if (isObj(result)) { // 是对象,继续创建响应式
return reactive(result)
}
return Reflect.get(target, prop)
},
测试例子及效果:
const {reactive} = VueCore
const orgin = {
age: 18,
name: 'lyh',
obj: {
test: '111'
}
}
const state = reactive(orgin)
console.log(state.obj)
是对象不继续代理的打印效果:
继续代理的效果:
可以看到obj属性也被代理了
effect
effect是响应式的核心,先看看用法:
effect(() => {
document.body.innerText = state.age
})
setTimeout(() => {
state.age = 19
}, 2000)
age初始是18,两秒后会重新渲染成19。看用法,我们想要的是在每次set的时候,触发effect函数执行,那怎么能触发到effect呢,我们在effect初始化的时候,把effect存起来,等set的时候,取出来执行。
简易实现:
export let bukect = []
export const effect = (fn: Function) => {
bukect.push(fn) // 将要执行的函数存起来
fn()
}
设置值的时候,将函数取出来执行:
export const handler = {
//...
set(target, prop, value) {
const result = Reflect.set(target,prop, value );
bukect.forEach(fn => {
fn() // 取出来执行
})
return result
}
};
这里已经能实现age改变触发页面更新了,但是有如下问题:
- effect里面只获取了age的值,如果我们改变name属性,也会触发effect再次执行,name没有在effect里面,我们希望不要执行
- 如果写两个effect,一个里面获取age,一个里面获取name,我们希望初始化时执行两个effect,渲染age和name,过一秒改变age,执行获取age的effect,而事实一秒后两个effect都执行了
分析问题:
从上面可以看出,只要初始化effect,就会往bukect存入函数,只要set,就会取出来执行。出问题的原因是effect和和数据源和属性没有关联,我们用一种数据结构把他们关联起来:
这样我们可以通过target[key]取到具体要执行的函数,改写代理代码:
export const handler = {
get(target, prop) {
const result = Reflect.get(target, prop)
if (isObj(result)) {
return reactive(result)
}
if (!activeEffect) return Reflect.get(target, prop) // 没有effect,直接返回值
let depMaps = bukect.get(target) // map: prop >> effect
if (!depMaps) {
depMaps = new Map() // map: prop >> effect
bukect.set(target, depMaps)
}
let deps = depMaps.get(prop) // effect
if (!deps) { // 没有effect列表,新建一个
depMaps.set(prop, deps = new Set())
}
deps.add(activeEffect)
return Reflect.get(target, prop)
},
set(target, prop, value) {
const result = Reflect.set(target,prop, value );
const depMaps = bukect.get(target) // map: prop >> effect
if (depMaps) {
const deps = depMaps.get(prop) // set: effect,根据target和prop取出的effect函数
deps && deps.forEach(fn => {
fn()
})
}
return result
}
};
上述第一个问题解决,第二个问题下次再探索 仓库地址, 欢迎讨论