有了实现useState的经验,我们还是从useEffect的使用方法上入手。

useEffect接收两个参数,一个是用于回调执行的的callback,一个是依赖项。

示例:

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

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

代码实现

我们根据使用方法,就可以定义出useEffect函数,并传递两个参数。

function useEffect(callback, deps) {
    let effectHook = {
        callback,
        deps,
    };
    // effectHook 挂载到wipFiber中
    wipFiber.effectHook = effectHook;
}

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

function commitRoot() {
    deletions.forEach(commitDeletion);
    commitWork(wipRoot.child);
    // 定义一个函数commitEffectHook用来调用useEffect
    commitEffectHook();
    wipRoot = null;
    deletions = [];
}

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

  1. 页面首次挂载后,会执行一次。(init)
  2. 依赖项发生改变的时候会执行。(update)

因此在commitEffectHook 的函数就可以根据两种情况来做

1、init

在commitEffectHook函数中,要获取到effectHooks中的callback,就需要去遍历整个dom树,因此需要一个递归函数。

function commitEffectHook() {
		// run 函数用于递归
    function run(fiber) {
        if (!fiber) return;
        // 如果callback函数存在的话就调用。
	      fiber.effectHook?.callback()
        run(fiber.child);
        run(fiber.sibling);
    }
    run(wipRoot);
}

**2、**update

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

因此我们可以通过是否存在alternate来判断是不是首次挂载。

// 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();
                }
            });
            
            const oldEffectHook = fiber.alternate.effectHook
            const needUpdate = oldEffectHook?.deps.some((oldDep,index) => {
	            return oldDep !== fiber.effectHook.deps[index]
            })
            
            needUpdate && fiber.effectHook.callback()
        }
				// 根据链表递归调用
        run(fiber.child);
        run(fiber.sibling);
    }

    run(wipRoot);
}