React中transitionend事件两次触发导致待办列表渲染异常如何解决
问题根因
- transitionend事件重复触发:
transitionend会对每个完成过渡的CSS属性各触发一次,同时你未对绑定的事件回调做解绑,多次操作会重复绑定回调,导致deleteTodo被多次执行。 - 列表key使用index:渲染列表时用数组下标作为key,删除元素后数组下标重排,React会错误复用DOM节点,直接导致页面渲染内容和state不匹配。
- TodoList过滤逻辑错误:你用普通变量存储过滤后的待办列表,未和props的变更做绑定,组件重新渲染时容易拿到旧的过滤结果。
- deleteTodo存在闭包隐患:更新todos时直接引用外部的todos变量,未使用函数式更新,可能拿到旧的state值导致删除结果不符合预期。
修复步骤
1. 修复TodoWrapper.js中的deleteTodo逻辑
将更新逻辑改为函数式写法,确保每次都拿到最新的state:
// TodoWrapper.js const deleteTodo = (todo) => { console.log(todo); setTodos(prevTodos => prevTodos.filter((item) => item.id !== todo.id)); };
同时优化filter的返回值,filter回调只需要返回布尔值即可,不需要返回对应项。
2. 修复TodoList.js中的transitionend重复触发问题
修改deleteAnimation函数,增加回调解绑逻辑,也可以指定监听的过渡属性避免误触发:
// TodoList.js const deleteAnimation = (todo) => { const selectedTodo = document.getElementById(todo.id); selectedTodo.classList.add('fall'); const handleTransitionEnd = (event) => { // 这里替换成你实际做动画的CSS属性,比如opacity/transform都可以 if (event.propertyName === 'transform') { props.deleteTodo(todo); selectedTodo.removeEventListener('transitionend', handleTransitionEnd); } } selectedTodo.addEventListener('transitionend', handleTransitionEnd); };
3. 替换列表key为唯一ID
修改列表渲染的key属性,使用todo.id代替数组下标:
// TodoList.js 列表渲染部分 {filteredTodos.map((todo) => { return ( <div className="todo" id={todo.id} key={todo.id}> {/* 其余列表内容保持不变 */} </div> ) })}
4. 优化待办列表过滤逻辑
用useMemo缓存过滤结果,自动监听props变更更新过滤列表:
// TodoList.js 顶部引入useMemo import { useMemo } from 'react'; // 组件内部删除原有的let todos、filterTodo等相关逻辑,替换为以下代码 const filteredTodos = useMemo(() => { switch (props.todosType) { case 'completed': return props.todos.filter(todo => todo.completed); case 'uncompleted': return props.todos.filter(todo => !todo.completed); default: return props.todos; } }, [props.todos, props.todosType]);
渲染列表时直接遍历filteredTodos即可。
内容的提问来源于stack exchange,提问作者Mohmad sabban
相关产品推荐
相关产品推荐

