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

React聊天应用优化:点击/关闭好友时避免全局重渲染及方案咨询

问题解决与最佳实践评估

问题现状

已通过memo包裹子组件并为handleFriendClick添加useCallback,解决了接收消息时其他Friend组件重渲染的问题,但点击/关闭好友对话框时所有Friend组件仍会全局重渲染。

核心原因分析

点击好友触发handleFriendClick时,会更新selectedFriends状态,而handleFriendClick的依赖数组包含selectedFriends,导致每次selectedFriends变化,handleFriendClick都会重新创建新的函数实例。由于FriendsList和Friend组件接收的handleFriendClick prop发生变化,即使其他好友的friend数据没有改动,所有Friend组件仍会触发重渲染。

解决方案

1. 稳定handleFriendClick函数,移除不必要的依赖

通过useRef跟踪最新状态+函数式更新,避免将selectedFriends放入useCallback的依赖数组,让handleFriendClick保持稳定:

const Home = () => {
  const { user, token } = useAuth();
  const [selectedFriends, setSelectedFriends] = useState([]);
  const { messages } = useWebSocket();
  const [friendsList, setFriendsList] = useState([]);
  
  // 用ref跟踪selectedFriends的最新值
  const selectedFriendsRef = useRef(selectedFriends);
  useEffect(() => {
    selectedFriendsRef.current = selectedFriends;
  }, [selectedFriends]);

  // ... 其他逻辑(latestMessage、processedMessagesRef、handleUpdateFriends)

  // 优化后的fetchMessagesForFriend,用useCallback稳定
  const fetchMessagesForFriend = useCallback(async (friend) => {
    try {
      const response = await axios.get(`http://localhost:8080/api/chat/messages/${friend.friendshipId}`, {
        params: { limit: 100 },
      });
      if (response.status === 204) {
        console.log("No messages found.");
      } else if (Array.isArray(response.data)) {
        const friendWithMessages = { 
          ...friend, 
          storedMessages: response.data.reverse(), 
          isMessageBoxOpen: true, 
          hasMessageBoxBeenOpenedOnce: true 
        };
        setSelectedFriends((prev) => {
          if (prev.length >= 2) {
            return [prev[1], friendWithMessages];
          }
          return [...prev, friendWithMessages];
        });
      }
    } catch (error) {
      console.error("Failed to fetch messages:", error);
    }
  }, []);

  // 优化后的handleFriendClick,依赖数组为空,函数保持稳定
  const handleFriendClick = useCallback(
    async (friend) => {
      const existingFriend = selectedFriendsRef.current.find((f) => f.id === friend.id);
      if (existingFriend) {
        if (existingFriend.isMessageBoxOpen) {
          return;
        } else if (existingFriend.hasMessageBoxBeenOpenedOnce) {
          // 用函数式更新获取最新状态,无需依赖selectedFriends
          setSelectedFriends((prev) => 
            prev.map((f) => 
              f.id === friend.id ? {...f, isMessageBoxOpen: true, unreadMessages: 0} : f
            )
          );
          setFriendsList((prev) => 
            prev.map((f) => 
              f.id === friend.id ? {...f, unreadMessages: 0} : f
            )
          );
          return;
        }
      }
      await fetchMessagesForFriend(friend);
      setFriendsList((prev) => 
        prev.map((f) => 
          f.id === friend.id ? {...f, unreadMessages: 0} : f
        )
      );
    },
    []
  );

  // ... 返回JSX
};

2. 简化Friend组件的回调逻辑

由于handleFriendClick已经稳定,Friend组件内无需额外用useCallback包裹点击回调:

let renderCount = 0;

const Friend = memo(({ friend, handleFriendClick }) => {
  console.log("Friend rendered", renderCount++);
  
  const handleClick = () => {
    try {
      handleFriendClick(friend);
    } catch (error) {
      console.log("Failed to fetch messages:", error);
    }
  };

  return (
    <li onClick={handleClick} key={friend.id} className="flex py-2 border-b border-gray-200 cursor-pointer hover:bg-gray-200 rounded-md">
      <div className="px-2"> 
        {friend.username.length > 20 ? friend.username.slice(0, 20) + "..." : friend.username} 
      </div> 
      {friend.unreadMessages > 0 && (
        <div className="bg-red-500 text-white rounded-full px-2 ml-2"> 
          {friend.unreadMessages} 
        </div>
      )}
    </li>
  );
});

最佳实践评估

现有实现的可取之处

  • 正确使用memo包裹子组件,减少不必要的重渲染
  • 使用useCallback稳定回调函数
  • 使用useRef跟踪已处理消息ID,避免重复处理WebSocket消息
  • 更新状态时使用函数式更新,确保获取最新状态

可改进的地方

  1. 状态拆分:selectedFriends和friendsList存在数据冗余(如unreadMessages),可将未读消息数单独维护为{ [friendId]: number }格式的状态,避免在两个数组中同步更新。
  2. WebSocket逻辑优化:handleUpdateFriends依赖selectedFriends和friendsList,可改为用函数式更新获取最新状态,移除这两个依赖,让函数更稳定。
  3. 错误处理:fetchMessagesForFriend的错误处理仅输出控制台,可添加用户可见的错误提示。
  4. 组件职责拆分:Home组件承担了过多逻辑(WebSocket处理、消息更新、API请求),可抽离为自定义Hook(如useChatMessages、useFriendList),提升代码复用性和可维护性。

内容的提问来源于stack exchange,提问作者lifecycles

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 09:45:54