React性能优化:实现仅重渲染更新的帖子而非全部帖子
问题:仅重渲染变更的帖子而非全部重渲染
我的应用从Node.js服务器获取帖子数据(服务器从MySQL读取),在Posts组件中通过映射渲染每个Post组件,每个Post接收对应数据。当帖子发生变更(比如新增点赞)时,Node.js通过WebSocket通知客户端重新获取更新后的帖子数组,但此时所有帖子都会重渲染,原因是传入了新数组。如何修改代码实现仅重渲染变更的帖子?
相关代码
useGetPosts
import { useState, useEffect } from "react"; import axios from "axios"; import io from "socket.io-client"; const socket = io("http://localhost:3001"); const useGetPosts = () => { const [posts, setPosts] = useState<IPost[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const getPosts = async () => { setLoading(true); try { const response = await axios("http://localhost:3001/activities/posts"); setPosts(response.data); } catch (error) { console.log(error); setError(true); } finally { setLoading(false); } }; useEffect(() => { getPosts(); socket.on("changedPosts", () => { getPosts(); }); return () => { socket.off("changedPosts"); }; }, []); return { posts, loading, error }; };
Posts 组件
export default function Posts() { const [postsAmmount, setPostsAmmount] = useState(5); const { posts, loading, error } = useGetPosts(); const postsToShow = posts.slice(0, postsAmmount); const handleClick = () => { setPostsAmmount((prev) => prev + 5); }; return ( <PostsWrapper> {error && "Unexpected error occurred - couldn't download posts."} {loading ? ( <Spinner pxSize={80} /> ) : ( postsToShow.map((item) => { return <Post post={item} key={item.postId} />; }) )} {postsAmmount < posts.length ? ( <div onClick={handleClick}>Show more</div> ) : ( "No more posts" )} </PostsWrapper> ); }
Post 组件
export default function Post({ post }: PostProps) { const { comments, commentsLoading, commentsError } = useGetComments( post.postId ); const { likePost, loading, error } = usePostActions(); const [areCommentsActive, setAreCommentsActive] = useState(false); const { isDarkMode } = useContext(DarkModeContext); const toggleComments = () => { setAreCommentsActive((prev) => !prev); }; return ( <PostWrapper isDarkMode={isDarkMode}> {commentsLoading ? ( <Spinner pxSize={40} /> ) : ( <> <CreatorWrapper> <ProfileIcon image={toBase64(post.profileImage.data)} /> <p> {post.firstName} {post.lastName} </p> </CreatorWrapper> <PostBodyWrapper>{post.postText}</PostBodyWrapper> <ReactionsAndComments> <LikesWrapper> {post.likes > 0 && ( <> <FontAwesomeIcon icon={faThumbsUp} /> {post.likes} {loading && <Spinner pxSize={30} />} </> )} </LikesWrapper> <CommentsCounterWrapper onClick={toggleComments}> {comments?.length! > 0 ? `${comments?.length} Comments` : "No comments yet"} {commentsError && "Couldn't get comments"} </CommentsCounterWrapper> </ReactionsAndComments> <InteractionsWrapper isDarkMode={isDarkMode}> <div onClick={() => likePost(post.postId)}> <FontAwesomeIcon icon={faThumbsUp} /> Like </div> <div onClick={toggleComments}> <FontAwesomeIcon icon={faMessage} /> Comment </div> </InteractionsWrapper> {areCommentsActive && ( <CommentsSection postId={post.postId} comments={comments} /> )} </> )} </PostWrapper> ); }
解决方案
1. 优化状态更新:仅替换变更的帖子
当前getPosts每次用全新数组替换原状态,导致所有Post组件因引用变化重渲染。修改setPosts逻辑,对比新旧数组,仅更新有变化的帖子,保留未变化帖子的引用:
const getPosts = async () => { setLoading(true); try { const response = await axios("http://localhost:3001/activities/posts"); const newPosts = response.data; setPosts(prevPosts => { // 先更新已有帖子中变化的项 const updatedExisting = prevPosts.map(oldPost => { const match = newPosts.find(newPost => newPost.postId === oldPost.postId); // 对比内容是否变化,变化则替换,否则保留原引用 return match && JSON.stringify(match) !== JSON.stringify(oldPost) ? match : oldPost; }); // 添加新出现的帖子 const newItems = newPosts.filter(newPost => !prevPosts.some(old => old.postId === newPost.postId) ); return [...updatedExisting, ...newItems]; }); } catch (error) { console.log(error); setError(true); } finally { setLoading(false); } };
2. 让Post组件成为纯组件
用React.memo包裹Post组件,当传入的post属性引用未变化时,组件不会重渲染:
import React from 'react'; // ... 组件内部代码保持不变 ... export default React.memo(Post);
3. 可选:优化WebSocket通知逻辑(需后端配合)
如果后端可以修改,让WebSocket推送仅变更的帖子数据(比如帖子ID和新点赞数),而非通知全量刷新,客户端直接更新对应帖子状态,性能更优:
useEffect(() => { getPosts(); // 假设后端推送单个变更的帖子对象 socket.on("postUpdated", (updatedPost) => { setPosts(prevPosts => prevPosts.map(post => post.postId === updatedPost.postId ? updatedPost : post ) ); }); return () => { socket.off("postUpdated"); }; }, []);
通过以上修改,只有内容发生变化的帖子会重渲染,未变化的帖子因引用未改变,会被React.memo跳过不必要的渲染。
内容的提问来源于stack exchange,提问作者user19786344
相关产品推荐
相关产品推荐

