从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(十) 收发消息

1.聊天框

首先我们完善前端的消息输入框

components下面新建MessageInput组件

javascript 复制代码
import { useState,useRef } from "react"
import {X,Image,Send} from "lucide-react"

import { useChatStore } from "../store/useChatStore"
import toast from "react-hot-toast"
const MessageInput = () => {
    const [text, setText] = useState("")
    const [imagePreview, setImagePreview] = useState(null) // 预览的图片
    const fileInputRef = useRef(null) // 文件输入框
    const {sendMessages} = useChatStore(); // 发送消息

    const handleImageChange =(e) => {
        const file = e.target.files[0]
        if(!file.type.startsWith("image/")) {
            toast.error("请选择图片文件")
            return
        }
        const reader = new FileReader()
        reader.onload = () => {
            setImagePreview(reader.result)
        }
        reader.readAsDataURL(file)
    }
    const removeImage = () => {
        setImagePreview(null)
        if(fileInputRef.current)  fileInputRef.current.value = ""
    }
    const handleSendMessage = async(e) => {
        e.preventDefault()
        if(!text.trim() && !imagePreview) return
        try {
            await sendMessages({text:text.trim(),image:imagePreview})
            toast.success("发送消息成功")
            setText("")
            setImagePreview(null)
            if(fileInputRef.current)  fileInputRef.current.value = ""
        } catch (error) {
            console.log(error)
            toast.error("发送消息失败:",error.message)
        }
    }
  return (
    <div className="p-4 w-full">
       {imagePreview && (
         <div className="mb-3 flex items-center gap-2">

            <div className="relative">
                <img 
                    src={imagePreview} 
                    alt="Preview"
                    className="size-20 object-cover rounded-lg border border-zinc-700"
                />
                <button
                    onClick={removeImage}
                    className="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-base-300
                    flex items-center justify-center"
                    type="button"
                >
                    <X className="size-3"/>
                </button>
            </div>
         </div>
       )}

       {/* 消息输入 */}
       <form onSubmit={handleSendMessage} className="flex items-center gap-2">
          <div className="flex-1 flex gap-2">
            <input
                type="text"
                className="w-full input input-bordered rounded-lg input-sm sm:input-md"
                placeholder="输入消息"
                value={text}
                onChange={(e) => setText(e.target.value)}
            />
            <input 
                type="file"
                accept="image/*"
                className="hidden"
                ref={fileInputRef}
                onChange={handleImageChange}
            />
            <button
                type="button"
                className={`hidden sm:flex btn btn-circle
                ${imagePreview?"text-emerald-500":"text-zinc-400"}`}
                onClick={() => fileInputRef.current.click()}
            >
                <Image size={20} />
            </button>
          </div>

           <button
            type="submit"
            className="btn btn-sm btn-circle"
            disabled={!imagePreview && !text.trim()}
           >
                <Send size={22}/>
           </button>
       </form>
    </div>
  )
}

export default MessageInput

聊天的气泡我们参考daisyUi的 chat-start chat-end 效果如下

ChatBox.jsx页面代码

javascript 复制代码
import {useEffect} from "react"
import { useChatStore } from "../store/useChatStore"
import { useAuthStore } from "../store/useAuthStore"
import {formatMessageTime} from "@/lib/util"
import ChatHeader from "./ChatHeader"
import MessageInput from "./MessageInput"

const ChatBox = () => {
  const {messages, getMessages, isMessagesLoading, selectedUser} = useChatStore()
  const {authUser} = useAuthStore()
  useEffect(()=>{
    getMessages(selectedUser._id)

  },[selectedUser._id, getMessages])

  if(isMessagesLoading) return <div>Loading...</div>
  return (
    <div className="flex-1 flex flex-col overflow-auto">
      {/* 聊天框头部 */}
      <ChatHeader/>
      
      {/* 聊天消息 */}
      <div className="flex-1 overflow-auto p-4 space-y-4">
          {messages.map((message)=> (
             <div
              key={message._id}
              // 消息的发送者id和当前用户id一致,则显示在右侧,否则显示在左侧
              className={`chat ${message.senderId===authUser._id ? 'chat-end' : 'chat-start'}`}
             >
              <div className="chat-image avatar">
                 <div className="size-10 rounded-full border">
                    <img
                      src={message.senderId === authUser._id ? authUser.profilePic || 'http://via.placeholder.com/150' : selectedUser.profilePic}
                      alt=""
                    />
                 </div>
              </div>

              <div className="chat-header mb-1">
                 <time className="text-xs opacity-50 ml-1">{formatMessageTime(message.createdAt)}</time>
              </div>
              {/* flex-col 图片和文字上下排列 */}
              <div className="chat-bubble flex flex-col"> 
                 {message.image && (
                   <img 
                    src={message.image}
                    alt=""
                    className="sm:max-w-[200px] rounded-md mb-2"
                   />
                 )}
                 {message.text && <p>{message.text}</p>}
              </div>
             </div>
          ))}

      </div>

      {/* 消息输入 */}
      <MessageInput/>
    </div>
  )
}

export default ChatBox

这是我们在左侧发送消息 右侧用户就能收到消息了 并且自己发送的消息在右侧 收到的消息在左侧 正是用了 chat-start chat-end 这2个class

我们的判断逻辑是

// 消息的发送者id和当前用户id一致,则显示在右侧,否则显示在左侧

className={`chat ${message.senderId===authUser._id ? 'chat-end' : 'chat-start'}`}

思考一个问题 目前我们发送消息给对方 只有刷新页面才能收到新的消息 这是不符合要求的 所以我们引入socket.io 实现实时 的 收发消息 功能。

下篇继续。。。

相关推荐
10年前端老司机13 分钟前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~16 分钟前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客1 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2451 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇6 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖6 小时前
http的缓存问题
前端·javascript·http
小小小小宇7 小时前
请求竞态问题统一封装
前端
loriloy7 小时前
前端资源帖
前端
源码超级联盟7 小时前
display的block和inline-block有什么区别
前端
GISer_Jing7 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js