xdm,又要到饭了,又更新代码了!
总结一下上一篇完成的内容,
- 改进了useState的实现, 解决了批量更新的性能提升。
有兴趣的可以点这里查看重构useState的实现 - 批量更新实现 (2)
这一篇,我们将会重构之前的useEffect, 配合fiber结构,从底层源码理解useEffect的运行机制,以及更新时机。
开始重构之前,我们先展示一下之前完成的useEffect代码,
const hooks = []
let currentHook = 0
function useEffect(effect, deps) {
const oldDeps = hooks[currentHook] ? hooks[currentHook].deps : undefined
const hasUpdated = !oldDeps || !deps.every((dep, i)=> dep===oldDeps[i])
if (hasUpdated) {
if (hooks[currentHook] && hooks[currentHook].cleanUp) {
hooks[currentHook].cleanUp()
}
const cleanup = effect();
hooks[currentHook] = {
deps,
cleanup
}
}
currentHook++
}
之前的代码已经可以很好的演示了基本的底层实现原理,但是没有配合fiber。
那么配合fiber的版本,源码如下,
function useEffect(effect, deps) {
// workInProgress, 当前处理的fiber 单元
const oldHook = workInProgress.alternate && workInProgress.alternate.hooks && workInProgress.alternate.hooks[workInProgress.currentHook];
const hasChanged = oldHook ? !deps || deps.some((dep,index) => dep!==oldHook.deps[index])
const hook = {
deps: deps,
cleanup: oldHook ? oldHook.cleanup : null
}
workInProgress.hooks.push(hook)
workInProgress.currentHook++
if (hasChanged) {
workInProgress.effects.push(hook)
}
}
- 从当前的fiber 的alternate (即当前的fiber树获取对应的hooks信息, 这里既是复用)
- 如果存在 (fiber.alternate === true) , 则获取hooks数组对应当前currentHook索引的那个hook (currentHook, 已经在前面的篇章提及了非常多, 其目的就是维护组件内部所有hooks运行的顺序保持不变,即一个组件内部可以存储在多个hooks,比如 useState / useEffect / ...)
- 那么对比当前work in progress 的fiber 与 current fiber 的deps,如果完全相同则hasChanged 返回false。如果deps空,则hasChanged 就是true。 这里也解释了,如果没有任何deps, useEffect总会运行。
- 接着把当前的hook添加进workInProgress.hooks 数组
- 更新 currentHook 索引
- 如果hasChanged === true, 则hook需要被添加进 effects数组里面(这个effects数组在 fiber数据结构上面定义)
我先回答一下这里你可能会有的疑问,避免由于你没有阅读之前的几篇可能产生的疑惑?
这里的effects 数组以及 hooks数组的区别
-
hooks数组:- 用途:存储所有 Hook 的状态和逻辑(包括
useState和useEffect)。 - 作用:确保在每次渲染时,Hook 的调用顺序和状态一致。
- 用途:存储所有 Hook 的状态和逻辑(包括
-
effects数组:- 用途:专门用于收集需要在提交阶段执行的副作用(即
useEffect的回调函数)。 - 作用:确保副作用在组件渲染完成后统一执行,并且只在依赖项发生变化时执行。
- 用途:专门用于收集需要在提交阶段执行的副作用(即
effects 数组的生命周期
-
收集阶段:
- 在渲染过程中,只有当
hasChanged为true时,hook会被添加到effects数组中。 - 这意味着,
effects数组只包含那些依赖项发生变化的副作用 Hook。
- 在渲染过程中,只有当
-
提交阶段:
-
在提交阶段,
commitWork函数会遍历effects数组,执行每个副作用。 -
执行完毕后,
effects数组会被清空:currentFiber.effects = []; -
这样,下一次渲染时,
effects数组将重新收集新的副作用,不会保留之前的未变化的副作用。
-
下一篇将从0-1开发useMemo 。
如果这样的长度/强度你觉得可以接受,觉得有帮助,可以继续阅读下一篇,实现一个 Mini React:核心功能详解 - useCallback的实现 。
如果文章对你有帮助,请点个赞支持一下!
啥也不是,散会。