什么是颗粒度更新
如果你使用过vue和react就会发现一个很明显的区别,即react的许多hook使用需要我们显示的指定一个数组参数,例如useEffect或useMemo的第二个参数
useMemo(() => a + b, [a, b])
而Vue中类似computed就不需要,原因是Vue能"自动追踪响应式依赖"。我们把这种能自动追踪依赖的技术称为颗粒度更新
实现一个简单的颗粒度更新
首先我们要知道我们要实现的最终目的,如下例子:
// useState返回值[0]是一个getter 可以方便我们后续的依赖收集操作
function useState(value) {
const getter = () => value
const setter = (nextValue) => value = nextValue
return [getter, setter]
}
const [count, setCount] = useState(0)
useEffect(() => {
// 打印count()值
console.log(count())
})
useEffect(() => {
console.log('无关effect')
})
setCount(2)// 这里我们希望能再次打印count()值 即执行count()相关的useEffect回调
实现的关键在于useEffect执行参数函数时如果执行了对应state的getter,我们要想办法收集到该执行参数函数,然后在state的setter调用改变value时再次执行前面收集到的函数即可,下面是具体实现和测试
const effectStack = []
function cleanup(effect) {
// 清除所有被收集的effect
effect.deps.forEach(subs => {
subs.delete(effect)
})
// 清除deps
effect.deps.clear()
}
function useEffect(cb) {
const execute = () => {
// execute每次执行重置依赖 类似Vue中的分支处理 避免多余effect收集 下面有相关文章链接
cleanup(effect)
// 将effec推入栈顶
effectStack.push(effect)
try {
cb()
} finally {
// 出栈
effectStack.pop()
}
}
const effect = {
execute,
deps: new Set()
}
// 立即执行一次
execute()
}
function useState(value) {
// 保存订阅该state的effects
const subs = new Set()
const getter = () => {
// 收集当前effect
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 建立订阅关系
subscribe(effect, subs)
}
return value
}
const setter = (nextValue) => {
value = nextValue
// 通知所有订阅该state的effect执行
// 注意此处不能使用subs原集合 因为执行execute会修改subs 导致进入死循环
;[...subs].forEach(effect => [
effect.execute()
])
}
return [getter, setter]
}
function subscribe(effect, subs) {
// 订阅关系建立
subs.add(effect)
// 依赖关系建立
effect.deps.add(subs)
}
const [count, setCount] = useState(0)
const [nm, setNm] = useState('lisi')
useEffect(() => {
console.log(count()) // 0
})
useEffect(() => {
console.log(nm()) // lisi
})
useEffect(() => {
console.log('11111')
})
setTimeout(() => {
setCount(2) // 2
setNm('zhangsan') // zhangsan
}, 2000);
上述关于Vue中的分支处理 避免多余effect收集的内容可以参考:juejin.cn/post/709566…