You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 22:30:56