React删除单个元素时未变更DOM节点重渲染且key未生效问题咨询
问题1:为什么未发生变更的前置元素会触发重渲染?
你混淆了两个不同的概念:React组件重渲染 和 浏览器重绘,你用Chrome的paint flashing标记的是浏览器重绘区域,不是React组件的重渲染行为,触发的原因有两个:
- 你的
List组件接收了完整的todos数组作为prop。每次删除操作更新todos数组时,所有List组件拿到的todos引用都会发生变化,默认情况下父组件状态更新后所有子组件都会触发重渲染,props变化更是会强制子组件重渲染,所以所有List都会执行重渲染逻辑。 - 你删除任意位置的元素后,该位置之后的所有DOM节点都会向上移动,浏览器需要对移动的区域进行重绘,所以你会看到删除位置下方的所有区域都被标记为重绘。
问题2:为什么设置了key属性也无法解决该重渲染问题?
key属性的作用仅在React的diff阶段:用来标记节点身份,判断哪些节点可以复用,避免不必要的DOM销毁/重建,它完全不干预组件的重渲染逻辑。你当前的key设置是正确的,已经避免了列表DOM的全量重建,但它管不到组件因为props变化触发的重渲染,也管不到浏览器因为元素位置移动触发的重绘。
优化方案
你可以按照以下步骤修改代码,避免不必要的组件重渲染:
- 修改
List组件的事件逻辑,移除对todosprop的依赖
将删除、完成逻辑改为使用setTodos的函数式更新,不需要传入完整的todos数组:
// List.jsx const deleteHandle = () => { setTodos(prev => prev.filter(el => el.id !== todo.id)); }; const completeHandle = () => { setTodos(prev => prev.map(el => { if (el.id === todo.id) { return { ...el, completed: !el.completed }; } return el; })); };
- 调整
List组件的传参,并用React.memo包裹组件
修改后List的props只有todo和稳定的setTodos,用React.memo包裹后,只有对应todo本身发生变化时,组件才会重渲染:
// List.jsx import React from 'react'; import "../todolist/css/TodoApp.css" const List = React.memo(({ todo, setTodos }) => { // 原有渲染、事件逻辑不变 }); export default List;
同时修改TodoApp中调用List的代码,去掉不必要的todos传参:
// TodoApp.jsx {todos.map(todo => { return ( <List setTodos={setTodos} todo={todo} key={todo.id} /> ); })}
- 如果还想减少浏览器的重绘,可以给每个
List节点设置固定高度,或者用CSS的content-visibility: auto属性优化渲染性能。
内容的提问来源于stack exchange,提问作者Nicolas Smith
相关产品推荐
相关产品推荐

