前言
大家好我是Fly哥,好久没输出点react的文章,本篇文章经过作者李坤峰授权转发, 为啥要写这篇,还是想起了字节的面试题,「如何写一个自定义测量hook」,当时给出的解决方案不太好,正好看到这篇文章,分享给大家,供大家学习。
原生API
MutationObserver
MutationObserver 创建一个观察器,提供了对监视DOM树更改的能力。用于监控DOM节点的变化,如 属性变化、子节点增删改、子树的变化等。
// 选择需要观察变动的节点 const targetNode = document.querySelector('#root'); // 观察器的配置(需要观察什么变动) const config = { attributes: true, // 观察属性变动 childList: true, // 观察目标子节点的变化,是否有添加或者删除 subtree: true,// 观察后代节点,默认为 false attributeFilter: ['style'] // 过滤需观察的属性 }; // 当观察到变动时执行的回调函数 const callback = function(mutationsList, observer) { // mutationsList 发生变化dom list for(let mutation of mutationsList) { if (mutation.type === 'childList') { console.log('A child node has been added or removed.'); } else if (mutation.type === 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.'); } } }; // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(callback); // 以上述配置开始观察目标节点 observer.observe(targetNode, config); // 之后,可停止观察 observer.disconnect();
ResizeObserver
ResizeObserver 创建一个新的ResizeObserver对象监听元素大小变化。专门用来观察DOM元素的尺寸是否发生了变化。
// 选择需要观察变动的节点 const targetNode = document.querySelector('#root'); // 创建一个观察器实例 const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { entry.target.style.borderRadius = Math.max(0, 250 - entry.contentRect.width) + 'px'; } }); // 开始观察目标节点 resizeObserver.observe(targetNode); // 取消全部元素监听 resizeObserver.disconnect()
react中监测dom元素大小变化
useSize
ahooks中的useSize封装了ResizeObserver,实现了对dom元素尺寸变化的监测
const size = useSize(target);
问题
以上API,无论是MutationObserver 还是ResizeObserver,仅支持静态dom的监听,即监听时此dom已存在。如果需要监测一个动态渲染的dom,则必须在元素渲染完成后实例化监测方法。在vue中,我们可以使用$nextTick保证dom加载完成后执行实例化方法,那么在react中,如何知道一个元素已经渲染完成呢?在react官方文档中给出了答案,我该如何测量 DOM 节点?useRef返回一个可变的 ref 对象,但它并不会把当前 ref 的值的 变化 通知到我们。获取 DOM 节点的位置或是大小的基本方式是使用 callback ref,它将在组件挂载和卸载时通知我们。
function MeasureExample() { // 记录dom高度 const [height, setHeight] = useState(0); // dom ready const measuredRef = useCallback(node => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, []); // 组件 return ( <> <h1 ref={measuredRef}>Hello, world</h1> <h2>The above header is {Math.round(height)}px tall</h2> </> ); }
在react中监测动态dom大小变化
Callback ref监测初始化时dom高度 + useSize监测dom变化,覆盖dom变化的全场景
let docHeight = 0// 记录dom高度 function MeasureExample() { // 强制render,初始化dom监听 const [, setDomReady] = useState(false) // 高度变化统一处理 const changeHeight = (h) => { // height=0,return if (!h || docHeight === h) return // dom高度发生变化 // TODO } // dom ready const measuredRef = useCallback(node => { if (node !== null) { // dom初始化获取高度 changeHeight(node.getBoundingClientRect().height) // 强制刷新,useSize绑定dom setDomReady(true) } }, [changeHeight]); // 实例化dom监听 const size = useSize(document.querySelector('#target')) useEffect(() => { // dom大小发生变化 changeHeight(size.height) }, [changeHeight, size]) // 组件 return ( <> { loading ? null : ( <h1 id="target" ref={measuredRef}>Hello, world</h1> )} <h2>The above header is {Math.round(height)}px tall</h2> </> ); }