React+SignalR实时应用滚动加载重复渲染问题求助
滚动加载SignalR数据时重复渲染相同内容的问题分析
核心原因分析
1. SignalR连接重复创建与事件重复绑定
你的useEffect依赖数组包含loading,每次loading状态切换(比如从true变为false),这个useEffect就会重新执行:
- 每次都会新建一个SignalR连接实例
- 重复绑定
ReceivePosts和SendPost事件 - 旧连接没有被销毁,导致同一个
GetPosts请求会被多个连接的事件监听处理,多次执行setPost([...prevPosts, ...post]),最终重复添加相同数据
2. Loading状态未及时锁定请求
在handleScroll触发分页请求前,没有先设置loading = true,导致滚动到页面底部时可能连续触发多次GetPosts调用,进一步加剧重复数据问题。
3. 分页参数与后端交互的潜在问题
如果后端的分页逻辑存在错误(比如page参数起始值处理不当,返回的分页数据与之前重复),也会导致前端重复渲染,但结合代码来看,前端连接重复是主要诱因。
修复方案
方案1:用useRef保存SignalR连接,避免重复创建
将连接实例存入useRef,确保整个组件生命周期内只创建一次连接:
export const Home = () => { const [post, setPost] = useState([]); const page = useRef(0); const [loading, setLoading] = useState(false); const connectionRef = useRef(null); // 保存连接实例 useEffect(() => { // 只在组件挂载时创建一次连接 if (!connectionRef.current) { const connection = new HubConnectionBuilder() .withUrl("http://localhost:5226/posts", { transport: HttpTransportType.WebSockets, }) .build(); connection.on("ReceivePosts", (newPosts) => { setPost((prevPosts) => [...prevPosts, ...newPosts]); setLoading(false); }); connection.on("SendPost", (newPost) => { setPost((prev) => [...prev, newPost]); // 单条数据无需展开数组 setLoading(false); }); connection.start() .then(() => { connection.invoke("GetPosts", page.current, 5); }) .catch((err) => console.log("Error: ", err.toString())); connectionRef.current = connection; } const handleScroll = () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight && !loading) { setLoading(true); // 先锁定loading,防止重复请求 page.current += 1; connectionRef.current.invoke("GetPosts", page.current, 3) .catch((err) => { console.log("Error: ", err.toString()); setLoading(false); }); } }; window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); // 组件卸载时断开连接 if (connectionRef.current) { connectionRef.current.stop(); connectionRef.current = null; } }; }, [loading]); return ( <> <div className="grid columns-10 justify-center row-auto gap-8 top-5"> <div className="w-full "> <PostLayout> <div className="w-full flex flex-col gap-8 "> {loading ? ( <div className="relative top-32"> <Loading /> </div> ) : ( post.map((prop) => { return ( <Post key={prop.id} // 用数据唯一ID代替index author={prop.author} text={prop.content} url={prop.url} image={prop.image} /> ); }) )} </div> </PostLayout> </div> </div> </> ); };
方案2:修正Loading状态的时机
在触发GetPosts前立即设置loading = true,防止滚动事件多次触发请求,这步已包含在上述代码中。
方案3:避免用index作为列表key
原代码用index作为Post组件的key,当列表数据新增时,React可能错误复用组件导致渲染异常。改用每个post的唯一标识(比如数据库中的id字段)作为key,确保列表渲染的正确性。
内容的提问来源于stack exchange,提问作者Atalay Özyıldırım
相关产品推荐
相关产品推荐

