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

React聊天应用组件无限请求后端致服务崩溃,求助排查

React聊天应用无限重复API请求问题修复

问题描述

开发React聊天应用时出现以下异常:

  • ChatWindow组件持续向后端发起重复请求,10秒内触发117次请求,导致后端崩溃,同时聊天记录被重复缓存
  • Sidebar组件存在完全相同的无限请求问题
  • 移除useEffect的依赖变量后,ChatWindow无法加载任何聊天记录

核心问题原因

两个组件的useEffect都触发了无限循环,根源是:将useState更新的状态变量作为了该useEffect的依赖项,形成闭环:

  1. useEffect触发数据请求
  2. 请求完成后调用setXXX更新状态
  3. 状态变化再次触发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为空时发起无效请求

修改后的代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:57:15