引言
之前了解了什么是副作用函数与Vue2、Vue3实现响应式数据的不同点,并且实现了最基础的响应式,但是也存在一些问题,今天解决一下,effect
固定,与无法与属性绑定的问题
副作用函数改进
之前是直接定义的一个effect
函数,也导致了他是固定的,改进如下
let activeEffect: Function
const effect = (fn: Function) => {
activeEffect = fn
fn()
}
这个函数,接受一个函数作为参数,函数内部中将传入的fn
赋值给外部作用域变量activeEffect
,并且调用一下作为函数的第一次执行,通过这种方式解决effect
固定的问题
buket
的改进
之前用一个Set
来作为存放effect
的桶,问题是无法根据代理的对象属性值来进行对应,需要一个以键值对形式来存数据的数据结构。调用上面创建副作用函数如下:
effect(() => {
app.innerHTML = `<div>name: ${proxy.name}: age: ${proxy.age}</div>`
})
这段代码中有三个重要的对象:
- 操作的代理对象
- 代理对象使用到的属性
- 副作用函数本身
其存在以下关系图
其中
target
代表代理的原始对象key
代表操作的属性effect
代表副作用函数
其关系是一个树形数据结构,之前的Set
不符合这种场景,改进为WeakMap
来做为buket
const buket = new WeakMap()
代理对象Getter/Setter
的改进
Getter
中存储三者关系Setter
中取出effect
并执行
完整代码如下
const bukets = new WeakMap<typeof data, Map<Keys, Set<Function>>>()
const data = {
name: "sakurige",
age: 18,
}
type Keys = keyof typeof data
let activeEffect: Function
const effect = (fn: Function) => {
activeEffect = fn
fn()
}
const proxy = new Proxy(data, {
get(target, key: Keys) {
// 如果没有副作用函数,直接返回key对应的值
if (!activeEffect) return target[key]
let depsMap: Map<Keys, Set<Function>> | undefined = bukets.get(target)
// 不存在对应的depsMap,则初始化,并重新赋值depsMap: 是一个Map类型
if (!depsMap) bukets.set(target, (depsMap = new Map()))
let effects: Set<Function> | undefined = depsMap.get(key)
// 不存在key对应的effects,则初始化,并重新赋值effects:是一个Map类型
if (!effects) depsMap.set(key, (effects = new Set()))
effects.add(activeEffect)
return target[key]
},
set(target, key: Keys, value) {
;(target[key] as any) = value
bukets
.get(target)
?.get(key)
?.forEach((fn) => fn())
return true
},
})
const app = document.querySelector("#app")!
const h1 = document.createElement("h1")
const h2 = document.createElement("h2")
const h3 = document.createElement("h3")
app.append(h1, h2, h3)
effect(() => {
h1.innerHTML = `<div>my age: ${proxy.age}</div>`
})
effect(() => {
h2.innerHTML = `<div>my name: ${proxy.name}</div>`
})
effect(() => {
h3.innerHTML = `<div>my name: ${proxy.name} is ${proxy.age}</div>`
})
const btn1 = document.querySelector(".btn1")!
const btn2 = document.querySelector(".btn2")!
btn1.addEventListener("click", () => {
proxy.age++
})
btn2.addEventListener("click", () => {
proxy.name = "zs" + new Date().getSeconds()
})
改变age
标题1,3发生改变,改变name
标题2,3发生改变
代码整理
上面对之前的响应式进行了改造,基本功能实现,接下来整理一下代码:
- 收集副作用函数的代码提取
- 触发副作用函数的代码提取
// track 收集副作用函数
const track = (target: typeof data, key: Keys) => {
// 判断是否存在activeEffect
if (!activeEffect) return //没有则直接返回
// 有则先获取target对应的依赖Map
let depsMap: Map<Keys, Set<Function>> | undefined = bukets.get(target)
if (!depsMap) bukets.set(target, (depsMap = new Map())) // 不存在则创建
// 根据key获取key对应的set
let deps: Set<Function> = depsMap.get(key)!
if (!deps) depsMap.set(key, (deps = new Set()))
deps.add(activeEffect)
}
// trigger 触发副作用函数
const trigger = (target: typeof data, key: Keys) => {
const deps = bukets.get(target)
if (!deps) return
deps?.get(key)?.forEach((effect: Function) => effect())
}
// setter getter 函数
const proxy = new Proxy(data, {
get(target, key: Keys) {
track(target, key)
return target[key]
},
set(target, key: Keys, value) {
;(target[key] as any) = value
trigger(target, key)
return true
},
})
结束语
这次对之前的基础版响应式进行了改进,但是并未细说Map
,Set
,WeakMap
之前的区别与用法,后续更在Es6
的知识当中,当然现在的响应式也不是完善的,还需要进行更一步改进喽,后续进行学习