如何正确检测React组件内单个元素的渲染情况(非组件渲染)
我知道可以通过React开发者工具检测组件渲染,也看过相关问题中提到的其他方法,但这些都不是我需要的。
我正在寻找检测组件内单个元素渲染的方法,例如当我们给一个映射生成的列表新增一个项时:
修改前:
<ul> <li key="1">Banana</li> <li key="2">Watermelon</li> <li key="3">Pear</li> </ul>
修改后:
<ul> <li key="0">Apple</li> <li key="1">Banana</li> <li key="2">Watermelon</li> <li key="3">Pear</li> </ul>
我知道React会在此时生成新的虚拟DOM,和之前的虚拟DOM对比后,以最少的变更更新浏览器DOM,不会重新渲染整个DOM。在这个场景下,通过使用key属性,React不会修改每一个子元素,会判断可以保留<li>Banana</li>、<li>Watermelon</li>和<li>Pear</li>对应的子树不变。
我需要准确查看这个渲染/插入的过程,我发现Chrome开发者工具(Shift+Ctrl+i => Elements面板)无法正确检测,因为当用原生JavaScript实现相同效果时,如果原生JS修改了所有子元素而非保留原有子树,Chrome开发者工具也无法区分这两种情况。
有没有方法可以正确检测元素的渲染?哪怕没有React相关工具,使用浏览器原生能力也可以。
附言:我指的不是组件渲染
以下是几种可以精确检测单个DOM元素变更的方法,大部分是浏览器原生能力,不需要依赖React工具:
- 方法1:使用Chrome DevTools DOM断点
打开Elements面板,找到你要监测的目标父元素(比如示例中的<ul>)或者现有子元素,右键选择「Break on」,可选三个触发条件:- Subtree modifications:子节点新增、删除、内容变更时触发断点
- Attribute modifications:节点自身属性变更时触发断点
- Node removal:节点被移除时触发断点
给示例中的<ul>加Subtree modifications断点,触发列表更新后,如果断点只在新增Apple的<li>时触发,现有三个<li>没有触发任何断点,就说明原有节点被完整保留。
- 方法2:使用Mutation Observer原生API
这个API可以监听DOM的所有细粒度变更,直接输出所有新增、删除、修改的节点信息,代码示例如下:
// 选择要监听的目标ul元素 const target = document.querySelector('ul'); // 配置监听范围:子节点变更、属性变更、子树内所有节点都监听、记录属性旧值 const observerConfig = { childList: true, attributes: true, subtree: true, attributeOldValue: true }; const observer = new MutationObserver((mutations) => { mutations.forEach(mut => { if(mut.type === 'childList') { mut.addedNodes.forEach(node => console.log('新增节点:', node.textContent || node)) mut.removedNodes.forEach(node => console.log('移除节点:', node.textContent || node)) } else if(mut.type === 'attributes') { console.log(`属性变更:节点${mut.target.textContent}的${mut.attributeName}从${mut.oldValue}改为${mut.target.getAttribute(mut.attributeName)}`) } }) }) // 启动监听 observer.observe(target, observerConfig) // 测试完成后记得销毁监听 // observer.disconnect()
运行代码后触发列表更新,控制台只会打印新增的Apple节点,没有任何原有节点的移除或属性变更日志,就能确认原有节点被保留。
- 方法3:给DOM节点手动打标记验证
这是最直观的验证方式,在列表更新前,打开控制台给原有节点加个自定义标记:
document.querySelector('li:first-child').dataset.customMark = '原始Banana节点'
列表更新后,再在控制台查询这个标记:
document.querySelectorAll('li')[1].dataset.customMark // 新增Apple后Banana变成第二个节点
如果能拿到之前设置的原始Banana节点值,就说明这个节点是原来的,没有被销毁重建。
- 方法4:React场景下用内联ref验证
如果是React项目,可以给列表项加临时的ref回调,ref回调只会在元素挂载和卸载时执行:
{list.map(item => ( <li key={item.id} ref={el => { if(el) console.log(`${item.name} 节点挂载`) else console.log(`${item.name} 节点卸载`) }} > {item.name} </li> ))}
新增Apple项时,控制台只会打印Apple 节点挂载,不会打印其他三个节点的挂载/卸载日志,即可证明原有节点没有被重新渲染。
内容的提问来源于stack exchange,提问作者Nicolas Smith

