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

Vue聊天功能发送消息后DOM未自动更新问题求助

问题描述

使用TypeScript、Firebase和Pinia开发聊天功能,用户可创建/加入房间进行消息交互,但发送消息后DOM不会自动更新,必须刷新页面。已尝试在Pinia store中为currentChatRoom使用ref并添加watcher,但无效果。

现有代码

Pinia Chat Store

import { defineStore } from 'pinia'
import axios from 'axios'
import type { ITask, INewtask } from "../types/Task"
import {
    getFirestore, collection,
    addDoc, deleteDoc, doc,
    onSnapshot, query, where,
    orderBy, serverTimestamp,
    updateDoc,
    arrayUnion,
    getDocs,
    setDoc
} from 'firebase/firestore'
import { Fireapp } from '@/main'
import { getAuth } from 'firebase/auth'
import { ref } from 'vue'
import type { DocumentData } from '@firebase/firestore'

export default defineStore('chat-store', {
    state: () => {
        return {
            chatRoom: {
                createChatName: "",
                messages: [],
            },
            messageInput: "",
            joinChatName: "",
            currentChatRoom: ref(null as DocumentData | null)
        }
    },
    actions: {
        CreateChatRoom() {
            const db = getFirestore(Fireapp)
            const chatRoomCollection = collection(db, "chatRoom")
            const auth = getAuth()
            const user = auth.currentUser

            const newChatRoom = {
                createChatName: this.chatRoom.createChatName,
                messages: this.chatRoom.messages,
                users: []
            }


            if (user) {
                const userId = user.uid
                addDoc(chatRoomCollection, {
                    createChatName: this.chatRoom.createChatName,
                    messages: this.chatRoom.messages,
                    users: []
                })
                    .then((docRef) => {
                        console.log("Create a Room", docRef.id)
                        this.currentChatRoom = {
                            id: docRef.id,
                            createChatName: this.chatRoom.createChatName,
                            messages: this.chatRoom.messages,
                            users: []
                        }
                    })
                    .catch((error) => {
                        console.error("Error adding document: ", error)
                    })
            }
            this.currentChatRoom = newChatRoom

        },
        async JoinChatRoom(joinChatName: string): Promise<void> {
            if (!joinChatName) {
                console.error('Invalid chat room name:', joinChatName)
                return
            }

            const db = getFirestore(Fireapp)
            const chatRoomCollection = collection(db, 'chatRoom')

            try {
                const querySnapshot = await getDocs(
                    query(chatRoomCollection, where('createChatName', '==', joinChatName))
                )

                if (querySnapshot.empty) {
                    console.log(`No such document with: ${joinChatName}`)
                    return
                }

                const doc = querySnapshot.docs[0]
                const chatRoomData = doc.data() as DocumentData
                console.log("joined Room", doc.id)

                this.currentChatRoom = {
                    id: doc.id,
                    createChatName: chatRoomData.createChatName,
                    messages: chatRoomData.messages || [],
                    users: chatRoomData.users || []
                }

                console.log('Chat room data:', this.currentChatRoom)
            } catch (error) {
                console.error("Error joining chat room: ", error)
                throw error
            }
        },

        async SendMessage() {
            try {
                await this.JoinChatRoom(this.joinChatName)

                const db = getFirestore(Fireapp)
                const auth = getAuth()
                const user = ref(auth.currentUser)

                if (this.messageInput === "" || this.messageInput === null || !this.currentChatRoom) {
                    return
                }

                const chatRoomId = this.currentChatRoom ? this.currentChatRoom.id : null
                console.log("this is the data", chatRoomId)
                const newMessage = {
                    messages: this.messageInput,
                }

                const chatRoomDoc = doc(db, 'chatRoom', chatRoomId)
                await updateDoc(chatRoomDoc, {
                    messages: arrayUnion(newMessage),
                })


                console.log("Message sent")
            } catch (error) {
                console.error("Error sending message: ", error)
            }
        },

        ListenChanges() {
            const db = getFirestore(Fireapp)

            if (this.currentChatRoom && this.currentChatRoom.id) {
                const chatRoomDoc = doc(db, 'chatRoom', this.currentChatRoom.id)
                onSnapshot(chatRoomDoc, (doc) => {
                    if (doc.exists()) {
                        this.currentChatRoom = doc.data()
                        console.log(doc.data)
                    } else {
                        console.log("No document was found")
                        this.currentChatRoom = { id: null, createChatName: "", messages: [], users: [] }
                    }
                })
            }
        }
    }
}
)

消息展示组件

<template>
    <div class="chat-container">
        <div> Chat </div>
        <div> {{ chatStore.chatRoom.createChatName }}</div>
        <div> {{ chatStore.joinChatName }}</div>
        <h4>Messages:</h4>
        <div
            v-for="(message, index) in messagesArr"
            :key="index"
        >{{ message.messages }}</div>
    </div>
    <form class="chat-input">
        <input
            type="text"
            v-model="chatStore.messageInput"
            name="messages"
            placeholder="write something..."
        >
        <button @click.prevent="chatStore.SendMessage()">Send</button>
    </form>
</template>

<script setup lang="ts">
import useChatStore from '@/stores/chat'
import { onMounted, ref, watch } from 'vue'

const chatStore = useChatStore()

let messagesArr = chatStore.currentChatRoom.messages

watch(
    () => chatStore.currentChatRoom.messages,
    (newVal) => {
        messagesArr.value = newVal
    }
)
onMounted(() => {
    chatStore.ListenChanges()
})

</script>

<style>
.chat-input input {
    width: 100%;
}

.chat-container {
    display: flex;
    height: 100%;
    flex-direction: column;
    align-items: center;
}
</style>
解决方法

1. 修复Pinia State响应式问题

Pinia的state本身已做响应式处理,禁止在state中嵌套ref,这会破坏Pinia的响应式追踪机制。修改currentChatRoom定义:

state: () => {
    return {
        chatRoom: {
            createChatName: "",
            messages: [],
        },
        messageInput: "",
        joinChatName: "",
        // 移除ref,明确类型包含文档ID
        currentChatRoom: null as (DocumentData & { id?: string }) | null
    }
},

2. 修正ListenChanges的数据更新逻辑

doc.data()不会返回文档ID,更新currentChatRoom时需手动合并ID字段,确保响应式更新完整:

ListenChanges() {
    const db = getFirestore(Fireapp)

    if (this.currentChatRoom && this.currentChatRoom.id) {
        const chatRoomDoc = doc(db, 'chatRoom', this.currentChatRoom.id)
        onSnapshot(chatRoomDoc, (doc) => {
            if (doc.exists()) {
                // 合并文档数据与ID
                this.currentChatRoom = {
                    id: doc.id,
                    ...doc.data()
                } as DocumentData & { id: string }
            } else {
                console.log("No document was found")
                this.currentChatRoom = { id: "", createChatName: "", messages: [], users: [] }
            }
        })
    }
}

3. 优化CreateChatRoom的冗余赋值

原代码中addDoc回调外的this.currentChatRoom = newChatRoom会覆盖带ID的房间数据,直接删除该行:

CreateChatRoom() {
    const db = getFirestore(Fireapp)
    const chatRoomCollection = collection(db, "chatRoom")
    const auth = getAuth()
    const user = auth.currentUser

    if (user) {
        addDoc(chatRoomCollection, {
            createChatName: this.chatRoom.createChatName,
            messages: [],
            users: []
        })
            .then((docRef) => {
                console.log("Create a Room", docRef.id)
                this.currentChatRoom = {
                    id: docRef.id,
                    createChatName: this.chatRoom.createChatName,
                    messages: [],
                    users: []
                }
                // 创建房间后立即启动监听
                this.ListenChanges()
            })
            .catch((error) => {
                console.error("Error adding document: ", error)
            })
    }
    // 删除 this.currentChatRoom = newChatRoom 这行代码
}

4. 修复组件的响应式监听与初始化

组件中messagesArr未处理空值情况,且监听目标不准确,直接简化为模板内可选链访问:

<template>
    <div class="chat-container">
        <div> Chat </div>
        <div> {{ chatStore.chatRoom.createChatName }}</div>
        <div> {{ chatStore.joinChatName }}</div>
        <h4>Messages:</h4>
        <!-- 用可选链处理空值,无需额外变量 -->
        <div
            v-for="(message, index) in chatStore.currentChatRoom?.messages || []"
            :key="index"
        >{{ message.messages }}</div>
    </div>
    <form class="chat-input">
        <input
            type="text"
            v-model="chatStore.messageInput"
            name="messages"
            placeholder="write something..."
        >
        <button @click.prevent="chatStore.SendMessage()">Send</button>
    </form>
</template>

<script setup lang="ts">
import useChatStore from '@/stores/chat'
import { onMounted } from 'vue'

const chatStore = useChatStore()

onMounted(() => {
    // 仅当已有房间数据时启动监听
    if (chatStore.currentChatRoom?.id) {
        chatStore.ListenChanges()
    }
})
</script>

5. 优化SendMessage的重复调用问题

原方法每次发送消息都会重复调用JoinChatRoom,添加判断避免冗余操作:

async SendMessage() {
    try {
        // 仅当当前房间与目标房间不一致时才执行加入操作
        if (!this.currentChatRoom || this.currentChatRoom.createChatName !== this.joinChatName) {
            await this.JoinChatRoom(this.joinChatName)
            this.ListenChanges()
        }

        const db = getFirestore(Fireapp)
        const auth = getAuth()
        const user = auth.currentUser

        if (!this.messageInput || !this.currentChatRoom) {
            return
        }

        const chatRoomId = this.currentChatRoom.id
        const newMessage = {
            messages: this.messageInput,
            // 建议添加发送者ID和时间戳,提升功能完整性
            senderId: user?.uid,
            timestamp: serverTimestamp()
        }

        const chatRoomDoc = doc(db, 'chatRoom', chatRoomId)
        await updateDoc(chatRoomDoc, {
            messages: arrayUnion(newMessage),
        })

        // 发送后清空输入框
        this.messageInput = ""
        console.log("Message sent")
    } catch (error) {
        console.error("Error sending message: ", error)
    }
}
关键原因总结
  1. Pinia state中嵌套ref导致响应式失效,Pinia会自动处理state的响应式,无需额外包装。
  2. onSnapshot更新时未保留文档ID,且未正确合并数据,组件无法追踪变化。
  3. 组件初始化未处理空值,监听目标不准确,无法响应currentChatRoom的整体替换。
  4. 重复调用JoinChatRoom干扰监听逻辑,且冗余操作影响性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 17:09:51