有了实现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时,正常情况下会有两种情况:
因此在commitEffectHook
的函数就可以根据两种情况来做
在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);
}
更新时要针对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);
}