React删除条目后无需重载全页实现组件局部刷新方案
问题原因
你当前实现删除后视图不更新的核心逻辑问题非常明确:
- 书籍列表的源状态
myBooks定义在父组件App中,AllBooks组件只是接收这个状态作为props渲染 - 删除操作执行后,你只更新了
AllBooks组件内部的lastDeletedTitle状态,没有对父组件中存储的书籍列表源数据做任何修改,传入AllBooks的booksprops始终是初始加载的旧数据,自然不会触发视图重渲染。
实现方案
二选一即可,根据你的业务场景选择:
方案1:本地过滤更新(推荐,无额外网络请求,性能更好)
核心思路是删除接口请求成功后,直接在原有状态基础上过滤掉已删除的条目,不需要重新请求全量数据。
修改步骤
- 在父组件
App中定义删除后更新列表的方法,将该方法通过props传递给AllBooks组件 - 删除接口调用成功后,调用父组件传入的更新方法,过滤掉已删除的书籍,React检测到状态变化会自动重渲染视图
- 移除不必要的
lastDeletedTitle状态,删除成功后直接触发toast提示即可
修改后代码
App.tsx
import React, { useLayoutEffect, useState } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import { getAllBooks } from "./api_actions/api_calls"; import "./App.css"; import AllBooks from "./components/AllBooks"; import InsertBooks from "./components/InsertBook"; import { Book } from "./models/Book"; function App() { const [myBooks, setMyBooks] = useState<Book[]>([]); // 新增:删除书籍后更新本地列表状态 const removeBookFromList = (deletedBookId: string | number) => { setMyBooks(prevBookList => prevBookList.filter(book => book.id !== deletedBookId)); }; useLayoutEffect(() => { getAllBooks().then((orders) => { setMyBooks(orders); }); }, []); return ( <div className="App"> <header className="App-header"> <BrowserRouter> <Routes> {/* 把更新方法透传给子组件 */} <Route path="/" element={<AllBooks books={myBooks} onDeleteSuccess={removeBookFromList} />} /> <Route path="/add" element={<InsertBooks />} /> </Routes> </BrowserRouter> </header> </div> ); } export default App;
AllBooks.tsx
import React from "react"; import { toast, ToastContainer } from "react-toastify"; import { deleteBook } from "../api_actions/api_calls"; import { Book } from "../models/Book"; import Card from "./Card"; // 扩展props类型定义 interface IAllBooksProps { books: Book[]; onDeleteSuccess: (deletedBookId: string | number) => void; } const AllBooks: React.FC<IAllBooksProps> = (props) => { const handleDeleteBook = (targetBook: Book) => { console.log("Trying to delete...", targetBook.title); // 注意:用书籍唯一id作为删除参数,不要用title,避免重名书籍删错 deleteBook(targetBook.id).then(() => { // 更新父组件的列表状态 props.onDeleteSuccess(targetBook.id); // 直接弹出删除成功提示 toast(`${targetBook.title} has been deleted!`); }); }; return ( <> {props.books?.map((book) => { return <Card key={book.id} book={book} onDelete={() => handleDeleteBook(book)} />; })} <ToastContainer /> </> ); }; export default AllBooks;
方案2:删除后重新拉取全量数据(适合多端数据同步场景)
如果你的应用存在多用户同时操作书籍数据的场景,需要保证前端展示和服务端数据完全一致,可以在删除成功后重新请求一次全量书籍列表覆盖原有状态。
修改步骤
- 把
App组件中拉取书籍列表的逻辑抽成独立可复用的方法 - 将这个拉取方法通过props传给
AllBooks组件 - 删除接口请求成功后,直接调用拉取方法重新获取全量书籍数据即可
核心修改点
App.tsx中抽离拉取逻辑:
const fetchBookList = () => { getAllBooks().then((orders) => { setMyBooks(orders); }); }; useLayoutEffect(() => { fetchBookList(); }, []); // 透传给子组件 <Route path="/" element={<AllBooks books={myBooks} refreshBookList={fetchBookList} />} />
AllBooks.tsx的删除回调中,删除成功后调用props.refreshBookList()即可。
注意事项
- 所有和列表源数据相关的更新操作,最终都要调用源状态对应的set方法(也就是这里的
setMyBooks)才能触发组件重渲染,子组件内部的独立状态修改不会影响父组件传入的props - 删除操作不要用书名作为唯一匹配条件,存在重名风险,必须使用后端返回的书籍唯一id
内容的提问来源于stack exchange,提问作者Ashar
相关产品推荐
相关产品推荐

