useEffect
useEffect是 React 中的一个 Hook,用于在组件渲染完成后执行副作用。它可以用来处理一些与组件渲染无关的操作,比如数据获取、事件监听、动画等。useEffect接受两个参数:一个函数和一个依赖数组。当依赖数组中的任何一个值发生变化时,useEffect中的函数都会被调用。如果依赖数组为空,则useEffect中的函数只会在组件渲染完成后被调用一次。useEffect中的函数可以返回一个清除函数,用于在组件卸载时清除副作用。如果依赖数组中的任何一个值发生变化,useEffect中的函数会先执行清除函数,然后再执行新的函数。
useLayoutEffect
useLayoutEffect是 React 中的一个 Hook,用于在浏览器布局和绘制之前同步执行副作用。它与useEffect类似,但是会在useEffect执行之后同步执行,而不是异步执行。useLayoutEffect的使用方式与useEffect类似,接受两个参数:一个函数和一个依赖数组。当依赖数组中的任何一个值发生变化时,useLayoutEffect中的函数都会被调用。 与useEffect不同的是,useLayoutEffect中的函数会在浏览器布局和绘制之前同步执行,这意味着它可以立即更新 DOM,从而避免出现闪烁等问题。但是,由于useLayoutEffect中的函数会同步执行,因此它的执行时间应该尽可能短,以避免阻塞浏览器渲染。
useEffect的实现原理
在 React Fiber 中,每个组件都对应一个 Fiber 节点,每个 Fiber 节点都有一个 Effect List,用于存储该组件的副作用。当组件渲染完成后,React 会将该组件的副作用添加到 Effect List 中。 在调度过程中,React 会遍历 Effect List,依次执行其中的副作用。如果副作用是一个函数,则直接执行该函数;如果副作用是一个对象,则根据对象中的标记执行相应的操作,比如添加、移动、删除 DOM 节点等。
useEffect的实现原理就是在组件渲染完成后,将该组件的副作用添加到 Effect List 中。当依赖数组中的任何一个值发生变化时,React 会先执行清除函数,然后再执行新的函数,将新的副作用添加到 Effect List 中。
function useEffect(effect, deps) {
const fiber = getCurrentFiber();
const effectList = fiber.effectList || (fiber.effectList = []);
const lastEffect = effectList[effectList.length - 1];
const newEffect = { effect, deps, next: null };
if (!lastEffect) {
effectList.push(newEffect);
} else if (depsChanged(lastEffect.deps, deps)) {
effectList.push(newEffect);
} else {
lastEffect.effect = effect;
}
}
function getCurrentFiber() {
// 获取当前 Fiber 节点
}
function depsChanged(oldDeps, newDeps) {
// 判断依赖数组是否发生变化
}
定义了一个名为 useEffect 的函数,它接受两个参数:一个函数和一个依赖数组。在函数内部,我们首先获取当前 Fiber 节点,然后获取该节点的 Effect List。如果 Effect List 不存在,则创建一个新的 Effect List。接着,我们判断该节点的 Effect List 中是否已经存在一个与当前函数相同的副作用,如果存在,则更新该副作用的依赖数组和函数;否则,将新的副作用添加到 Effect List 中。
在 useEffect 函数中,我们还定义了一个名为 depsChanged 的辅助函数,用于判断依赖数组是否发生变化。该函数接受两个参数:旧的依赖数组和新的依赖数组。如果两个依赖数组的长度不同,则说明依赖数组发生了变化;否则,我们遍历新的依赖数组,判断每个依赖项是否与旧的依赖数组中的对应项相同,如果存在不同的项,则说明依赖数组发生了变化。
useLayoutEffect
React Fiber 中,
useLayoutEffect与useEffect的实现原理基本相同,只是在调度过程中的执行时机不同。在执行 Effect List 时,React 会先执行useLayoutEffect中的副作用,然后再执行useEffect中的副作用。