React聊天应用组件无限请求后端致服务崩溃,求助排查
React聊天应用无限重复API请求问题修复
问题描述
开发React聊天应用时出现以下异常:
- ChatWindow组件持续向后端发起重复请求,10秒内触发117次请求,导致后端崩溃,同时聊天记录被重复缓存
- Sidebar组件存在完全相同的无限请求问题
- 移除useEffect的依赖变量后,ChatWindow无法加载任何聊天记录
核心问题原因
两个组件的useEffect都触发了无限循环,根源是:将useState更新的状态变量作为了该useEffect的依赖项,形成闭环:
- useEffect触发数据请求
- 请求完成后调用setXXX更新状态
- 状态变化再次触发useEffect执行,循环往复
具体对应:
- Sidebar中,useEffect依赖
menuItems,但fetchFriends内部会调用setMenuItems修改该状态 - ChatWindow中,useEffect依赖
chats,但fetchChats内部会调用setChats修改该状态
修复方案
1. 修复Sidebar组件
- 将useEffect的依赖从
menuItems改为空数组,仅在组件挂载时请求一次好友列表;如果需要后续更新好友列表,可通过WebSocket通知或手动刷新按钮触发,而非自动轮询 - 给动态生成的Menu.Item添加唯一key(用好友邮箱),避免React渲染警告
2. 修复ChatWindow组件
- 将useEffect的依赖从
chats改为recipientEmail和client,仅当聊天对象切换或当前用户变化时,才重新请求聊天记录 - 添加请求前置校验,避免recipientEmail为空时发起无效请求
修改后的代码
Sidebar.jsx
import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { Menu, Drawer, Button } from 'react-daisyui'; const api = `http://localhost:8000`; function Sidebar({ onRecipientChange }) { const [menuItems, setMenuItems] = useState([]) const [nick, setNick] = useState('') const handleFriendSelect = (nickname, email) => { onRecipientChange(nickname, email) } const fetchFriends = async () => { if (!window.localStorage.getItem('e')) { const response = await axios.get(`${api}/users/profile/friends?email=${window.localStorage.getItem('mail')}`); const res = response.data.values; const frnds = JSON.parse(res); let items = []; if (Array.isArray(frnds) && frnds.length > 0 && res !== '["Start"]') { const requests = frnds.map(async (friend) => { const r = await axios.get(`${api}/users/profile?email=${friend}`); return r.data.values[0].nickname; }); const nicknames = await Promise.all(requests); items = nicknames.map((nickname, index) => ( <Menu.Item onClick={() => handleFriendSelect(nickname, frnds[index])} style={{ borderRadius: 3 }} key={frnds[index]} // 添加唯一key > {(nickname) ? nickname : "None"} </Menu.Item> )); } else { items.push( <Menu.Item key="None" style={{ borderRadius: 3 }}> None </Menu.Item> ); } setMenuItems(items); } } useEffect(() => { fetchFriends() }, []) // 改为空数组,仅组件挂载时请求一次 useEffect(() => { async function fetchNick() { if (!window.localStorage.getItem('e')) { const res = await axios.get(`${api}/users/profile?email=${window.localStorage.getItem('mail')}`) setNick(res.data.values[0].nickname) } } fetchNick() }, []); return ( <> <div> <Drawer open={true} side={ <> <Menu className="bg-base-200 rounded-sm text-lg h-full" style={{ width: 250 }}> <Menu.Title className='text-xl' style={{ color: 'gray' }}> <span className='flex justify-between'> {nick} <Button color='ghost' size='sm' className='btn-circle' onClick={async () => { window.localStorage.removeItem('mail') window.location = '/login' }} > </Button> </span> </Menu.Title> <Menu.Item> <Menu.Title className='text-lg'>Me</Menu.Title> <Menu.Item style={{ borderRadius: 3 }}> Profile </Menu.Item> <Menu.Item style={{ borderRadius: 3 }}> Inbox </Menu.Item> <Menu.Item style={{ borderRadius: 3 }}> Friends </Menu.Item> </Menu.Item> <Menu.Item> <Menu.Title className='text-lg'>Chats</Menu.Title> {menuItems} </Menu.Item> </Menu> </> } /> </div> </> ); } export default Sidebar
ChatWindow.jsx
import { useEffect, useState } from 'react'; import { Button, Navbar, Input, Join, ChatBubble } from 'react-daisyui'; import axios from 'axios' const api = `http://localhost:8000`; function ChatWindow({ recipientNickname, recipientEmail }) { const client = window.localStorage.getItem('mail') const [chats, setChats] = useState([]) async function fetchChats() { // 避免空参数发起无效请求 if (!recipientEmail || !client) return; try { const response = await axios.get(`${api}/users/messages?client=${client}&recipient=${recipientEmail}`); setChats(response.data || []); } catch (e) { console.log(e); setChats([]); } } useEffect(() => { fetchChats() }, [recipientEmail, client]) // 仅聊天对象或当前用户变化时请求 const chatBubbles = chats.map((message, index) => ( <ChatBubble key={index} end={message.client === client}> <ChatBubble.Message>{message.message}</ChatBubble.Message> </ChatBubble> )) return ( <div style={{ margin: 250, marginTop: 0, zIndex: 1 }}> <div> <Navbar> <Navbar.Center style={{ alignItems: 'center' }}> <Button tag="a" color="ghost" className="normal-case text-xl"> {recipientNickname || "Chats"} </Button> </Navbar.Center> </Navbar> </div> <div className='chats'> {chatBubbles} </div> <div> <div style={{ position: 'fixed', bottom: 5, left: 0, right: 0 }}> <Join> <Input style={{ width: 1000, marginLeft: 260 }} placeholder='Send a message' className='join-item' /> <Button className='join-item rounded-s' color='success'> Send </Button> </Join> </div> </div> </div> ) } export default ChatWindow
额外优化建议
- 给axios添加请求拦截器,实现相同请求的防抖或取消重复请求,避免并发请求冲突
- 聊天记录实时更新建议使用WebSocket,由后端主动推送新消息,替代前端轮询方案
- 后端可添加缓存机制,对相同的聊天记录查询请求直接返回缓存结果,降低服务器压力
内容的提问来源于stack exchange,提问作者Vaidik Rungta
相关产品推荐
相关产品推荐

