React列表如何仅渲染新项而非重渲染整个列表
React列表项定时器重置问题修复
问题说明
当列表中存在多个Todo项时,每添加新项都会导致所有已有项重渲染,进而重置它们的删除定时器,引发逻辑混乱:连续添加两项后,最后一项先被删除,随后列表消失,第一项才延迟删除。
已尝试无效方案
- 使用
React.memo()和React.useCallback()阻止重渲染,但因数组重建导致属性更新,无效 - 仅传递项的单个属性而非整个对象,无改善
代码示例
index.js
import { useState } from "react"; import ReactDOM from "react-dom/client"; import Todos from "./Todos"; const App = () => { const [todos, setTodos] = useState([]); const addTodo = () => { setTodos((todos) => [ ...todos, { id: new Date().getTime(), message: new Date().getSeconds() } ]); }; const deleteTodo = (todoID) => { const newTodos = todos.filter((todo) => todo.id !== todoID); setTodos((t) => [...newTodos]); }; return <Todos todos={todos} addTodo={addTodo} deleteTodo={deleteTodo} />; }; const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<App />);
Todos.js
import Todo from "./todo"; const Todos = ({ todos, addTodo, deleteTodo }) => { console.log("\nTodoList render"); return ( <> <h2>My Todos</h2> {todos.map((todo, index) => { return ( <Todo key={index} todomessage={todo.message} id={todo.id} deleteTodo={deleteTodo} /> ); })} <button onClick={addTodo}>Add Todo</button> </> ); }; export default Todos;
todo.js
import { useEffect } from "react"; const Todo = ({ todomessage, id, deleteTodo }) => { useEffect(() => { console.log("todo " + todomessage + " rendered"); setTimeout(() => { deleteTodo(id); console.log("todo " + todomessage + " rendered 3s ago"); }, 3000); }); return <p>{todomessage}</p>; }; export default Todo;
分步修复
1. 固定定时器执行时机
Todo组件的useEffect没有依赖数组,每次重渲染都会新建定时器。添加空依赖数组确保定时器仅在组件挂载时创建一次,同时添加清理函数避免内存泄漏:
// todo.js 修改后 import { useEffect } from "react"; const Todo = ({ todomessage, id, deleteTodo }) => { useEffect(() => { console.log("todo " + todomessage + " rendered"); const timer = setTimeout(() => { deleteTodo(id); console.log("todo " + todomessage + " rendered 3s ago"); }, 3000); return () => clearTimeout(timer); }, []); return <p>{todomessage}</p>; }; export default Todo;
2. 替换列表项key值
使用数组索引作为key会在列表项删除时引发后续项重渲染,改为使用Todo的唯一id:
// Todos.js 修改后 {todos.map((todo) => { return ( <Todo key={todo.id} todomessage={todo.message} id={todo.id} deleteTodo={deleteTodo} /> ); })}
3. 稳定deleteTodo函数引用
deleteTodo每次渲染都会重建,导致Todo组件无法通过React.memo优化。用useCallback包裹并使用函数式更新避免依赖:
// index.js 修改后 import { useState, useCallback } from "react"; // ... const deleteTodo = useCallback((todoID) => { setTodos(prevTodos => prevTodos.filter(todo => todo.id !== todoID)); }, []);
4. 启用React.memo优化
给Todo组件包裹React.memo,阻止不必要的重渲染:
// todo.js 修改后 import { useEffect, memo } from "react"; const Todo = ({ todomessage, id, deleteTodo }) => { // 原逻辑 }; export default memo(Todo);
修复后效果
每个Todo项仅在挂载时创建一次定时器,3秒后自动删除;添加新项时旧项不会重渲染,定时器不会被重置,所有项按自身挂载时间正常执行删除逻辑。
内容的提问来源于stack exchange,提问作者Wawa
相关产品推荐
相关产品推荐

