可以根据他的功能来一点一点的实现

Step1.组件加载后执行useEffect

const Foo = () => {
    React.useEffect(() => {
        console.log('init')
    }, [])

    return (
        <div> Foo </div>
    )
}

实现

将调用useEffect传递的函数与参数存起来

function useEffect(callback, deps) {
    let effectHook = {
        callback,
        deps,
    };
    wipFiber.effectHook = effectHook;
}

useEffect的执行时机是在dom挂载完成后执行的,所以可以在函数 commitRoot 中的 commitWork 后执行,也就是dom挂载完成后执行。

commitEffectHook 方法用来执行useEffect,在使用react的useEffect时,正常情况下会有两种情况:

  1. 页面首次挂载后,会执行一次。(init)

  2. 依赖项发生改变的时候会执行。(update)

    更新时要针对useEffect的依赖项进行判断,如果依赖项发生了改变,才会调用,如果没有发生改变则不调用

// commitWork 后执行此方法
function commitEffectHook() {
    function run(fiber) {
        if (!fiber) return;
				// 如果fiber中没有alternate属性,则表示是首次挂载
        if (!fiber.alternate) { // init
            fiber.effectHook?.callback();
        } else { // update
						// 对effectHook中的deps遍历对比oldDeps,如果不相等则调用callback
            fiber.effectHook?.deps.forEach((newDep, index) => {
                const oldDeps = fiber.alternate.effectHook?.deps;
                if (oldDeps[index] !== newDep) {
                    fiber.effectHook.callback();
                }
            });
        }
				// 根据链表递归调用
        run(fiber.child);
        run(fiber.sibling);
    }

    run(wipRoot);
}

Step2.多个useEffect的调用

如果由多个useEffect进行了调用的话,按照上面的逻辑,后面的就会覆盖掉前面的,所以借鉴useState的实现,可以设置一个数组,将useEffect存储起来,然后循环调用即可。

实现:

let effectHooks;  //设置一个数组
function useEffect(callback, deps) {
    let effectHook = {
        callback,
        deps,
    };
    effectHooks.push(effectHook);
    wipFiber.effectHooks = effectHooks;
}
function commitEffectHook() {
    function run(fiber) {
        if (!fiber) return;

        if (!fiber.alternate) { // init
            if (fiber.effectHooks) {
								// 循环调用
                fiber.effectHooks.forEach((effect) => {
                    effect.callback();
                });
            }
        } else { // update
						// 双重循环,新值与旧值进行对比,不同则调用
            const oldEffectHooks = fiber.alternate.effectHooks;
            fiber.effectHooks?.forEach((newEffect, index) => {
                if (newEffect.deps.length) {
                    oldEffectHooks[index].deps.some((dep, i) => {
                        const needUpdate = dep !== newEffect.deps[i];
                        needUpdate && (newEffect.cleanup = newEffect.callback());
                    });
                }
            });
						
        }

        run(fiber.child);
        run(fiber.sibling);
    }

    run(wipRoot);
}

Step3.useEffect中的cleanup

useEffect cleanup 是Hook中的一个函数,它允许我们在卸载组件之前整理代码

useEffect 挂钩可以返回一个函数。

cleanup 可防止内存泄漏并删除一些不必要和不需要的行为。