近期被一个神奇的需求折腾了很久。经过不断尝试后,确定需要用到 MutationObserver API,加之之前还没使用过 useLayoutEffect,突然产生了对这三个 API 触发时机的好奇心。也顺便梳理下在尚不明晰触发时机时就写好的业务逻辑是否足够稳健?
这里简单介绍一下这三个 API,如果需要可直接点击跳转到相关文章复习一下要点。
useEffect
这个 API 对于 React 开发者来说应该再熟悉不过了,应该是被最广泛使用的 Hook 了,为函数组件注入了“副作用”的灵魂。从个人经历来看,也简化了此前类组件中关于挂载和更新的生命周期的调用。
useLayoutEffect
整个没使用过,不过直接引用官方的说法已经足够深刻了:
The signature is identical to useEffect, but it fires synchronously after all DOM mutations.
MutationObserver
MDN 官方文档对其描述如下:
The
MutationObserverinterface provides the ability to watch for changes being made to the DOM tree.
即该 API 提供了监听 DOM 变化的能力。
requestAnimationFrame
MDN 官方文档对其描述如下:
The
window.requestAnimationFrame()method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.
核心是在下次重绘前调用。
触发时机
可以看到,挂载时触发顺序:(MutationObserver 不会触发)
useLayoutEffect -> requestAnimationFrame -> useEffect
状态更新时触发顺序:
useLayoutEffect -> MutationObserver -> requestAnimationFrame -> useEffect
想来也应该是如此触发顺序,熟悉 React 相关实现应该不用像我这么麻烦了。因此,还是有必要再补一下 React 底层相关实现才行,有些东西真不是光看 API 说明就行的 _(:з」∠)_
因为 useLayoutEffect 和 MutationObserver 都是同步更新的,又 requestAnimationFrame 是在下次重绘前调用,所以不会像 useEffect 那样导致页面闪烁。因此,可以在 useLayoutEffect,MutationObserver 和 requestAnimationFrame 的回调中做一些骚操作。比如同步修改父容器的宽高,当前自身业务实现中用到了 MutationObserver 同步修改了父容器的宽高,这里就不赘述了。不过这篇文章写完重新梳理了一下,好像可以重构成使用 useLayoutEffect 或者 requestAnimationFrame 更好一些,感觉使用 MutationObserver 实在过于骚了,喜提 TODO +1。
谨以这篇短文纪念一下这令人窒息的骚操作 🤣