You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 14:57:16