React 列表删除后自动滚动到顶部

FreeGuideOnline 最新 2026-07-05

jsx import { useRef } from 'react';

const listContainerRef = useRef(null);

// JSX 中:

{/* 列表项渲染 */}
```

步骤二:在删除函数中执行滚动

在删除项的点击事件处理函数里,完成状态更新后,通过 ref.current 调用 scrollTo 方法。务必在状态更新之后触发滚动,因为 React 的状态更新是异步的,我们需要确保 DOM 已经更新(即列表已经变短)。可以使用 requestAnimationFramesetTimeout 来保证滚动发生在渲染之后,但更简洁的方式是使用 useLayoutEffect 监听列表长度,或直接在事件处理函数的微任务中执行。

这里提供一个稳妥的做法:利用 setTimeout(零延迟)将滚动操作推迟到当前宏任务之后,此时 React 已完成渲染。

const handleDelete = (id) => {
  // 假设 setItems 是 useState 返回的更新函数
  setItems(prev => prev.filter(item => item.id !== id));

  // 延迟滚动,确保 DOM 已更新
  setTimeout(() => {
    if (listContainerRef.current) {
      listContainerRef.current.scrollTo({
        top: 0,
        behavior: 'smooth',   // 平滑滚动
      });
    }
  }, 0);
};

步骤三:使用 useLayoutEffect 监听列表变化(可选)

如果你希望删除任何一项都自动滚动到顶部,而不需要在每个删除函数中编写滚动代码,可以通过 useLayoutEffect 监听列表长度(或其他依赖),当列表变化时自动触发滚动。useLayoutEffect 会在 DOM 更新后、浏览器绘制前同步执行,避免了闪烁。

import { useLayoutEffect } from 'react';

useLayoutEffect(() => {
  if (listContainerRef.current) {
    listContainerRef.current.scrollTop = 0; // 或使用 scrollTo
  }
}, [items]); // items 是列表状态

但要注意:如果列表新增项也会触发此 effect,可能导致每次添加都跳到顶部,不符合预期。因此,这个方案仅适用于“任何数据变化都回归顶部”的场景。更精确的控制还是推荐在删除函数中手动调用滚动。

完整示例代码

下面是一个包含添加和删除功能的待办事项列表,删除后会平滑滚回顶部。

import { useState, useRef } from 'react';

export default function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: '学习 React' },
    { id: 2, text: '实现自动滚动' },
    { id: 3, text: '完成项目' },
    // ... 更多列表项
  ]);
  const listRef = useRef(null);

  const handleDelete = (id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
    // 删除后滚回顶部
    setTimeout(() => {
      listRef.current?.scrollTo({
        top: 0,
        behavior: 'smooth',
      });
    }, 0);
  };

  const addTodo = () => {
    const newId = Date.now();
    setTodos(prev => [...prev, { id: newId, text: `新任务 ${newId}` }]);
    // 如果希望在添加时也自动滚到底部,可以在这里处理
  };

  return (
    <div>
      <button onClick={addTodo}>添加任务</button>
      <div
        ref={listRef}
        style={{
          maxHeight: '300px',
          overflowY: 'auto',
          border: '1px solid #ccc',
          padding: '8px',
        }}
      >
        {todos.map(todo => (
          <div key={todo.id} style={{ padding: '12px 0', borderBottom: '1px solid #eee' }}>
            {todo.text}
            <button onClick={() => handleDelete(todo.id)} style={{ marginLeft: 16 }}>
              删除
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}