Vue로 PWA 개발 - 그랜파 개발자
로그인 후에는 마이로그의 댓글에 대해 답글을 쓸 수 있습니다. 댓글의 답글에 대한 댓글을 쓰는 것은 지원하지 않습니다. 마이로그는 마이로그, 댓글, 답글의 구조를 가지고 있습니다. 물론 하나의 마이로그에 대해 여러 댓글이 가능하고, 하나의 댓글에 대해 여러 답글은 가능합니다.
댓글에 대한 답글 기능을 구현하려면, 댓글마다 답글(reply)을 저장하고 관리해야 합니다. 이를 위해 Firestore에서는 각 댓글마다 하위 컬렉션으로 답글을 저장하는 방식으로 설계할 수 있습니다. 댓글의 하위 컬렉션에 답글을 추가하고, 댓글과 함께 답글을 화면에 표시하도록 Vue.js에서 구현하는 방법을 설명하겠습니다.
Firestore의 댓글마다 하위 컬렉션으로 답글을 저장하는 구조를 사용할 수 있습니다. 기본적으로 Firestore에서 게시물(Post) -> 댓글(Comment) -> 답글(Reply) 구조로 데이터를 관리하게 됩니다.
Vuex에서 댓글과 유사하게 답글도 Firestore에 저장하고 관리할 수 있습니다. 각각의 댓글(commentId)에 대해 하위 컬렉션으로 답글을 추가하거나 가져오는 방식입니다.
import { getFirestore, collection, addDoc, serverTimestamp, query, orderBy, getDocs } from "firebase/firestore";
const db = getFirestore();
const store = new Vuex.Store({
state: {
comments: [], // 댓글 목록
},
mutations: {
setComments(state, comments) {
state.comments = comments;
},
addComment(state, comment) {
state.comments.push(comment);
},
addReply(state, { commentId, reply }) {
const comment = state.comments.find(c => c.id === commentId);
if (comment) {
if (!comment.replies) {
comment.replies = [];
}
comment.replies.push(reply);
}
},
},
actions: {
// 댓글 불러오기
async fetchComments({ commit }, postId) {
try {
const q = query(collection(db, "posts", postId, "comments"), orderBy("createdAt", "asc"));
const querySnapshot = await getDocs(q);
const comments = [];
querySnapshot.forEach((doc) => {
comments.push({ id: doc.id, ...doc.data(), replies: [] });
});
commit('setComments', comments);
} catch (error) {
console.error("댓글 가져오기 실패:", error);
}
},
// 댓글에 답글 추가
async addReply({ commit }, { postId, commentId, content, author }) {
try {
const replyRef = await addDoc(collection(db, "posts", postId, "comments", commentId, "replies"), {
content: content,
author: author,
createdAt: serverTimestamp(),
});
const newReply = { id: replyRef.id, content, author, createdAt: new Date() };
commit('addReply', { commentId, reply: newReply });
} catch (error) {
console.error("답글 추가 실패:", error);
}
},
// 특정 댓글의 답글 불러오기
async fetchReplies({ commit }, { postId, commentId }) {
try {
const q = query(collection(db, "posts", postId, "comments", commentId, "replies"), orderBy("createdAt", "asc"));
const querySnapshot = await getDocs(q);
const replies = [];
querySnapshot.forEach((doc) => {
replies.push({ id: doc.id, ...doc.data() });
});
replies.forEach(reply => {
commit('addReply', { commentId, reply });
});
} catch (error) {
console.error("답글 불러오기 실패:", error);
}
},
},
});
export default store;
<template>
<div>
<h3>Comments</h3>
<!-- 댓글 목록 -->
<ul v-if="comments.length > 0">
<li v-for="comment in comments" :key="comment.id">
<strong>{{ comment.author }}</strong>: {{ comment.content }}
<ul v-if="comment.replies && comment.replies.length > 0">
<li v-for="reply in comment.replies" :key="reply.id">
<strong>{{ reply.author }}</strong>: {{ reply.content }}
</li>
</ul>
<!-- 답글 작성 폼 -->
<div>
<input v-model="newReplies[comment.id]" placeholder="Write a reply..." />
<button @click="submitReply(comment.id)">Reply</button>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
postId: {
type: String,
required: true,
},
},
data() {
return {
newReplies: {}, // 댓글별 답글 입력 값
};
},
computed: {
comments() {
return this.$store.state.comments;
},
},
methods: {
async submitReply(commentId) {
const author = "Anonymous"; // 여기서 사용자 정보를 가져올 수 있음
const content = this.newReplies[commentId];
if (content && content.trim()) {
await this.$store.dispatch('addReply', {
postId: this.postId,
commentId: commentId,
content: content,
author: author,
});
this.newReplies[commentId] = ''; // 입력 필드 초기화
}
},
},
mounted() {
// 컴포넌트가 로드되면 해당 게시물의 댓글을 불러옴
this.$store.dispatch('fetchComments', this.postId);
},
};
</script>
import { updateDoc, doc } from "firebase/firestore";
async updateReply({ postId, commentId, replyId, newContent }) {
const replyRef = doc(db, "posts", postId, "comments", commentId, "replies", replyId);
try {
await updateDoc(replyRef, {
content: newContent,
updatedAt: serverTimestamp(),
});
console.log("답글이 수정되었습니다.");
} catch (error) {
console.error("답글 수정 실패:", error);
}
}
import { deleteDoc, doc } from "firebase/firestore";
async deleteReply({ postId, commentId, replyId }) {
const replyRef = doc(db, "posts", postId, "comments", commentId, "replies", replyId);
try {
await deleteDoc(replyRef);
console.log("답글이 삭제되었습니다.");
} catch (error) {
console.error("답글 삭제 실패:", error);
}
}
댓글마다 답글 기능을 추가하기 위해 하위 컬렉션을 사용해 Firestore에 데이터를 저장하고 불러올 수 있습니다.
Vuex의 actions를 통해 댓글의 하위 컬렉션에 답글을 추가하고, 상태 관리로 댓글과 답글을 화면에 반영합니다.
추가로 답글 수정 및 삭제 기능을 구현하여 답글에 대한 기능을 확장할 수 있습니다.
이 구조를 사용하면 댓글에 대한 답글을 Firestore에서 관리하며, 실시간으로 UI에 반영할 수 있습니다.