React点击按钮无需State重新渲染列表,解决setState异步滞后问题
解决点击「Mark Read」按钮后实时更新items状态的问题
核心问题分析
你当前的代码直接修改items变量不会触发React组件重渲染——React仅在状态(useState/useReducer)或props变化时才会重新渲染。之前用useState觉得状态更新滞后,是因为没正确使用函数式状态更新,也没配合TRPC mutation的回调来同步状态。
正确实现方案
1. 将处理后的items存入useState
把原本的普通变量items改成useState,确保状态变化能触发重渲染:
import { useState, useEffect } from 'react'; const Main: React.FC<MainProps> = (props) => { // Get All Feeds const feeds = trpc.feed.getAllFeeds.useQuery(); // Get Items let unsorteditems; if (props.FeedType == "one") { unsorteditems = trpc.feed.getItemsFromFeed.useQuery({ feedId: props.feedId }); } else if (props.FeedType == "readalready") { unsorteditems = trpc.feed.getReadItems.useQuery(); } else { unsorteditems = trpc.feed.getFeedItemsFromAllFeeds.useQuery(); } // 用useState存储处理后的items const [processedItems, setProcessedItems] = useState<typeof items>(undefined); // 当查询数据变化时,重新处理并更新状态 useEffect(() => { if (!unsorteditems.data || !feeds.data) return; // 先拷贝数组再排序,避免修改原始查询数据 const sorted = [...unsorteditems.data].sort( (objA, objB) => Number(objB.createdAt) - Number(objA.createdAt) ); const newItems = sorted.map((item) => { const feed = feeds.data.find((feed) => feed.id === item.feedId); return { ...item, feedTitle: feed?.title, feedLogoUrl: feed?.LogoUrl, markedAsRead: false, }; }); setProcessedItems(newItems); }, [unsorteditems.data, feeds.data]); // ...其余代码 }
2. 优化Mark Read的点击逻辑
使用函数式setState确保基于最新状态修改,同时在TRPC mutation成功后同步后端数据:
// Mark Read function const markreadmutation = trpc.feed.markReadUsingUpsert.useMutation({ onSuccess: () => { // 失效对应查询的缓存,让TRPC自动拉取最新数据(可选,保证前后端数据一致) switch (props.FeedType) { case "one": trpc.feed.getItemsFromFeed.invalidate({ feedId: props.feedId }); break; case "readalready": trpc.feed.getReadItems.invalidate(); break; default: trpc.feed.getFeedItemsFromAllFeeds.invalidate(); } }, }); const handleMarkRead = (itemId: string, index: number) => { markreadmutation.mutate({ itemId }); // 立即更新本地状态,实现UI即时反馈 setProcessedItems(prevItems => { if (!prevItems) return prevItems; return prevItems.map((item, i) => i === index ? { ...item, markedAsRead: true } : item ); }); };
3. 修改渲染逻辑
将渲染时的items替换为processedItems:
return ( <> <div className="basis-5/12 border-l border-slate-300/40"> {/* 省略标题部分代码 */} <div> {processedItems?.length === 0 && <p>No Items</p>} {processedItems?.map((item, i) => ( <div className={`border-slate-300/4 relative flex h-28 flex-col border-b ${item.markedAsRead ? "bg-gray-200" : "bg-white"}`} key={item.id} > <button onClick={() => handleMarkRead(item.id, i)}> [Mark Read] </button> </div> ))} </div> </div> </> );
额外优化建议
- 不要直接修改原始查询数据(比如
unsorteditems.data?.sort(...)),sort会改变原数组,应先拷贝再排序:[...unsorteditems.data].sort(...)。 - 可给查询添加
enabled选项,确保依赖数据存在时再发起请求,避免无效请求。
内容的提问来源于stack exchange,提问作者Michael Kremenetsky
相关产品推荐
相关产品推荐

