看的后面有的没看懂, 于是总结下之前的, 思路连贯, 就清晰了
初设计
无响应式却更新
obj.ok ? obj.text : 'not'
obj.ok = false
依赖应和 obj.text 没什么关系
所以, 每次调用 effect 函数的时候, 首先清除依赖, 如下方法
function cleanup(effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i]
deps.delete(effectFn)
}
effectFn.deps.length = 0
}
set 循环
const set = new Set()
set.forEach(item => {
set.delete(1)
set.add(1)
console.log('死循环中......')
})
解决
const set = new Set()
const newSet = new Set(set) // Set 接受一个可迭代对象
newSet.forEach(item => {
set.delete(1)
set.add(1)
console.log('不会死循环了')
})
所以, trigger 中增加代码: const effectsToRun = new Set(effects)
effect 嵌套
effect(() => {
console.log('effect1 run')
effect(() => { // 该函数后执行, 因此会覆盖 activeEffect
console.log('effect2 run')
obj.foo // 修改, 全打印
})
obj.bar // 修改, console.log('effect2 run')
})
解决 effect函数下
const effectFn = () => {
cleanup(effectFn)
activeEffect = effectFn
effectStack.push(effectFn)
const res = fn()
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
return res
}
无限递归
effect(() => {
obj.foo = obj.foo + 1
})
读取 track 修改 trigger 刚刚 tack 的函数 即 obj.foo = obj.foo + 1
track => trigger => track => trigger …
所以, 在 trigger 下的 forEach 中
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
代理对象
代理对象键
-
in => has
-
for/in => ownKeys for/in 的目的是迭代对象的键 所以, tarck 时候的 key 为
ITERATE_KEY
通过类似 obj.foo = 123 方式, 有两种可能的情况
- 键已存在, 修改值
- 添加一个新的键值对
如果是第一种情况, 不做处理, 因为对 for/in 迭代出来的键没有影响
第二种情况, 在 set 的时候判断
Obejct.prototype.call(target, key) ? 'SET' : 'ADD'
该 key 是否存在在 target 上 是的话, 执行 key 为ITERATE_KEY
的副作用函数
- delete => deleteProperty
代理对象值
因为有如下可能的情况
NaN === NaN // false
在 set 中有
set(target, key, newVal, receiver) {
const oldVal = target[key]
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
trigger(target, key, type, newVal)
}
}
继承二次触发
const child = reactive({ child: 1 })
const parent = reactive({ parent: 2 })
Object.setPrototypeOf(child, parent)
effect(() => {
console.log(child.parent)
})
child.parent = 3 // 打印两次 3
在 set(target, key, newVal, receiver)
中 target 为原始对象, 是变化的 而 receiver 为代理对象, 是不变的
所以 get 中 if (key === 'raw') return target
if (target === receiver.raw) trigger
代理数组
代理 length
arr.length // 响应数据
arr[2] = 123 // 添加数据
set 中
Number(key) >= target.length
type = ADD
trigger 中 遍历 depsMap.get('length').forEach(f => f())
if (type === 'ADD' && Array.isArray(target)) {
const lengthEffects = depsMap.get('length')
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
代理单个数组数组 arr[0]
arr[2] = 123 // 响应数据
arr.length = 0 // 删除数据
trigger 中 递归 arr 下的所有 key的所有 set
if (Array.isArray(target) && key === 'length') {
depsMap.forEach((effects, key) => {
if (key >= newVal) { // 代理数据自身的 key (如上例中的 2) >= 0 (arr.length 设置的值) 说明此时数据已经删除
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
for/in
arr[1] = 123 // add
arr.length = 0 // delete
上述本质上都是修改数组 length 属性
所以, 如果 for/in 为数组, 执行之后, 触发 length 的 trigger
ownKeys
中 track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
for/of
get 中 typeof key !== 'symbol'
防止使用 length 和 Symbol.iterator 比较
effect(() => {
for (const val of arr) {
console.log(val)
}
})