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

如何在Node.js中通过Socket实现React群聊的‘用户正在输入’提示

实现群聊中“用户正在输入”的Socket提示功能

后端(Node.js + Socket.io)

先在Node.js服务端配置Socket.io,处理客户端的输入状态事件,再广播给同房间的其他用户:

  1. 初始化Socket.io服务
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: "http://localhost:3000" // 替换为你的React前端地址
  }
});

// 存储每个用户的输入超时定时器,避免重复触发
const typingTimeouts = new Map();

io.on('connection', (socket) => {
  // 用户加入指定群聊房间
  socket.on('join-room', (roomId, username) => {
    socket.join(roomId);
    socket.roomId = roomId;
    socket.username = username;
  });

  // 处理用户正在输入事件
  socket.on('user-typing', () => {
    const { roomId, username } = socket;
    // 清除之前的超时定时器,防止重复发送停止输入事件
    if (typingTimeouts.has(username)) {
      clearTimeout(typingTimeouts.get(username));
      typingTimeouts.delete(username);
    }

    // 广播给同房间其他用户,告知当前用户正在输入
    socket.to(roomId).emit('user-typing', username);

    // 设置3秒超时,无新输入则自动触发停止输入
    const timeout = setTimeout(() => {
      socket.to(roomId).emit('stop-typing', username);
      typingTimeouts.delete(username);
    }, 3000);
    typingTimeouts.set(username, timeout);
  });

  // 手动触发停止输入(比如用户点击输入框外)
  socket.on('stop-typing', () => {
    const { roomId, username } = socket;
    if (typingTimeouts.has(username)) {
      clearTimeout(typingTimeouts.get(username));
      typingTimeouts.delete(username);
    }
    socket.to(roomId).emit('stop-typing', username);
  });

  // 用户断开连接时清理状态
  socket.on('disconnect', () => {
    const { username } = socket;
    if (typingTimeouts.has(username)) {
      clearTimeout(typingTimeouts.get(username));
      typingTimeouts.delete(username);
    }
    if (socket.roomId) {
      socket.to(socket.roomId).emit('stop-typing', username);
    }
  });
});

server.listen(3001, () => {
  console.log('Server running on port 3001');
});

前端(React)

在React组件中连接Socket.io,监听输入框状态、发送输入事件,并渲染正在输入的提示:

  1. 安装Socket.io客户端
npm install socket.io-client
  1. React组件实现
import { useState, useEffect, useRef } from 'react';
import { io } from 'socket.io-client';

const ChatRoom = ({ roomId, username }) => {
  const [message, setMessage] = useState('');
  const [typingUsers, setTypingUsers] = useState(new Set());
  const socketRef = useRef(null);
  const typingTimeoutRef = useRef(null);

  useEffect(() => {
    // 初始化Socket连接
    socketRef.current = io('http://localhost:3001'); // 替换为你的后端地址

    socketRef.current.emit('join-room', roomId, username);

    // 监听其他用户正在输入的事件
    socketRef.current.on('user-typing', (user) => {
      setTypingUsers(prev => new Set(prev).add(user));
    });

    // 监听其他用户停止输入的事件
    socketRef.current.on('stop-typing', (user) => {
      setTypingUsers(prev => {
        const updated = new Set(prev);
        updated.delete(user);
        return updated;
      });
    });

    return () => {
      socketRef.current.disconnect();
    };
  }, [roomId, username]);

  // 处理输入框变化,发送正在输入事件(防抖处理)
  const handleInputChange = (e) => {
    setMessage(e.target.value);

    if (typingTimeoutRef.current) {
      clearTimeout(typingTimeoutRef.current);
    }
    // 输入停止500ms后再发送事件,避免频繁触发
    typingTimeoutRef.current = setTimeout(() => {
      socketRef.current.emit('user-typing');
    }, 500);
  };

  // 输入框失焦时发送停止输入事件
  const handleInputBlur = () => {
    socketRef.current.emit('stop-typing');
    if (typingTimeoutRef.current) {
      clearTimeout(typingTimeoutRef.current);
    }
  };

  // 渲染正在输入的提示文本
  const renderTypingHint = () => {
    const usersArray = Array.from(typingUsers);
    if (usersArray.length === 0) return null;
    if (usersArray.length === 1) {
      return <p className="typing-hint">{usersArray[0]}正在输入...</p>;
    } else {
      return <p className="typing-hint">{usersArray.join('、')}正在输入...</p>;
    }
  };

  return (
    <div className="chat-room">
      <div className="chat-messages">
        {/* 聊天消息列表渲染区域 */}
      </div>
      {renderTypingHint()}
      <input
        type="text"
        value={message}
        onChange={handleInputChange}
        onBlur={handleInputBlur}
        placeholder="输入消息..."
      />
      <button onClick={() => {
        // 发送消息逻辑
        socketRef.current.emit('send-message', roomId, username, message);
        setMessage('');
        // 发送消息后触发停止输入
        socketRef.current.emit('stop-typing');
      }}>发送</button>
    </div>
  );
};

export default ChatRoom;

关键细节说明

  • 防抖/超时逻辑:前后端都加入了防抖处理,避免频繁发送Socket事件,减少服务器压力和前端UI频繁更新。
  • 房间隔离:通过join-room事件让用户加入指定房间,确保“正在输入”提示只在当前群聊内显示。
  • 状态清理:用户断开连接、输入停止或发送消息时,及时清理定时器并广播停止输入事件,避免提示残留。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 06:40:22