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) } }
关键原因总结
- Pinia state中嵌套
ref导致响应式失效,Pinia会自动处理state的响应式,无需额外包装。 onSnapshot更新时未保留文档ID,且未正确合并数据,组件无法追踪变化。- 组件初始化未处理空值,监听目标不准确,无法响应
currentChatRoom的整体替换。 - 重复调用
JoinChatRoom干扰监听逻辑,且冗余操作影响性能。
内容的提问来源于stack exchange,提问作者Njthon
相关产品推荐
相关产品推荐

