可以根据他的功能来一点一点的实现
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时,正常情况下会有两种情况:
页面首次挂载后,会执行一次。(init)
依赖项发生改变的时候会执行。(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);
}
如果由多个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);
}
useEffect cleanup 是Hook中的一个函数,它允许我们在卸载组件之前整理代码
useEffect 挂钩可以返回一个函数。
cleanup 可防止内存泄漏并删除一些不必要和不需要的行为。