하루 하나씩 작성하는 TIL #5
필자가 작성한 코드를 리뷰해보겠다. js를 구체적으로 리뷰할 생각인데, 이유가 무엇이냐하면
이 사진 한 장으로 설명이 된다.
<script src="https://kit.fontawesome.com/49bf1aaa3f.js" crossorigin="anonymous"></script>
<script src="/script/headerLoad.js"></script>
<script src="/script/footerLoad.js"></script>
Fontawesome 아이콘과 웹 페이지의 헤더와 푸터를 로드해준다. (헤더와 푸터를 처리하는 js를 따로 짜놓은 상태)
<script>AOS.init();</script>
앞서 설명했던 aos 초기화
<script type="module">
import { initializeApp } from "firebase/app";
import { getFirestore, collection, addDoc, serverTimestamp, deleteDoc, doc, getDocs, getDoc, updateDoc } from "firebase/firestore";
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-auth-domain",
projectId: "your-project-id",
storageBucket: "your-storage-bucket",
messagingSenderId: "your-messaging-sender-id",
appId: "your-app-id"
};
// Firebase 앱 초기화
const app = initializeApp(firebaseConfig);
// Firestore 데이터베이스 인스턴스 가져오기
const db = getFirestore(app);
</script>
Firebase SDK를 사용하여 앱을 초기화하고, 데이터베이스에 연결하는 과정. 앱의 구성은 firebaseConfig에 저장되어 있으며 이 정보를 사용하여 Firebase 서비스에 연결해준다.
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
Firebase 앱을 초기화하고 Firestore 데이터 베이스에 연결해준다. firebaseConfig 객체는 Firebase 프로젝트의 구성을 포함하고 있으며, 이를 사용하기 위해선 Firebase 앱을 초기화 해줘야 한다.
아래 코드를 리뷰하기전에,
제이쿼리는 오픈 소스 기반 자바스크립트 라이브러리로, 클릭 이벤트, 체인지 이벤트 등 웹 페이지 동작의 기능을 조작할 때 브라우저의 영향을 받지 않고 원하는 기능을 제작할 수 있도록 해준다.
브라우저의 버전에 따라 작동하지 않는 코드를 jQuery로 변경하여 사용하면 브라우저 문제 없이 사용할 수 있다. (=크로스 브라우징 기능 탑재)
필자는 제이쿼리를 겉핥기 식으로만 공부한 상태에서 사용하였기 때문에 이 부분에 대해 구체적으로 리뷰하고자 한다.
$(document).ready(function () {
$("#postComment").click(async function () {
// 이벤트 핸들러 내용
});
});
문서가 준비되었을 때, 실행될 코드를 익명함수로 전달한다. $() 함수는 jQuery 함수로, CSS 선택자를 사용하여 HTML 요소를 선택한다. 여기서 $("#postComment")는 id가 "postComment"인 HTML 요소를 선택한다.
내 코드에선
<button id="postComment"
class="py-3 px-5 bg-[#1D886D] text-[#eeeeee] float-end my-2 rounded-md hover:cursor-pointer">댓글달기</button>
이 부분이다. 즉 버튼이 클릭되었을 때 실행되는데,
const nickname = $("#nickname").val();
const password = $("#password").val();
const comment = $("#comment").val();
// 필드가 비어 있는지 확인
if (!nickname || !password || !comment) {
alert('아이디, 비밀번호, 댓글을 모두 입력해주세요.');
return;
}
// 비밀번호 길이 확인
if (password.length < 4) {
alert('비밀번호를 4자리 이상으로 입력해주세요.');
return;
}
댓글 작성 폼에서 닉네임, 비밀번호, 댓글을 가져온 후 (위와 같이 다 id값), 3개 다 비어있지는 않은지, 비밀번호가 4자리 이상인지 확인해준다.
#은 아이디 선택자를 나타내며 html 요소를 고유하게 식별하는데 사용된다.
.val()은 주로 입력 요소(input, select, textarea 등)에서 값을 가져오거나 설정하는 데 사용된다.
const data = {
nickname: nickname,
password: password,
comment: comment,
createdAt: serverTimestamp(), // 현재 시간 저장
};
try {
// Firebase에 데이터 추가
await addDoc(collection(db, "kdyComments"), data);
alert('댓글 작성 완료');
showComments(); // 댓글 다시 로드
// 입력 필드 리셋
$("#nickname").val('');
$("#password").val('');
$("#comment").val('');
} catch (error) {
console.error("Error adding document: ", error);
}
showComments();
입력 받은 내용을 객체로 만들어서 저장한 뒤, serverTimestamp() 함수를 사용하여 현재 시간을 createdAt 속성에 저장한다.
Firestore의 addDoc() 함수를 사용하여 데이터 베이스의 "kdyComments" 컬렉션에 데이터를 추가한다. 앞서 만든 데이터 객체인 data를 전달하여 컬렉션에 저장한다. await 키워드를 사용하여 비동기적으로 데이터를 추가하고, 추가가 완료될 때까지 기다린다.
alert('댓글 작성 완료');
showComments(); // 댓글 다시 로드
// 입력 필드 리셋
$("#nickname").val('');
$("#password").val('');
$("#comment").val('');
showComments() 함수를 호출하여 새로운 댓글이 추가된 화면을 다시 로드한다. 입력 필드의 값도 비워준다.
} catch (error) {
console.error("Error adding document: ", error);
}
데이터 추가 중에 오류가 발생하면, 오류 메시지를 콘솔에 출력해준다.
// 수정 버튼 클릭 시 이벤트 처리
$(document).on("click", ".edit-comment", async function () {
const docId = $(this).data("doc-id");
const currentComment = $(this).closest(".comment-item").find(".comment-text").text();
.edit-comment 클래스를 가진 요소가 클릭되었을 때 이벤트를 처리한다.
클릭된 수정 버튼의 data-doc-id 속성을 사용하여 해당 댓글의 ID를 가져온 후, 사용자에게 비밀번호를 입력받는다.
입력된 비밀번호와 해당 댓글의 비밀번호를 Firebase에서 비교하여 일치하면 사용자에게 수정할 새로운 댓글을 입력받는다.
입력된 새로운 댓글을 Firebase에서 해당 댓글의 데이터를 업데이트하고, 업데이트가 성공하면 화면에 알림을 표시하고, 댓글을 다시 로드하여 화면에 업데이트된 댓글 목록을 표시한다.
async function은 JavaScript에서 비동기 작업을 수행하는 함수를 정의할 때 사용되는 키워드이다. 이 함수는 비동기 함수로서, 내부에서 await 키워드를 사용할 수 있다.
$(this)는 클릭된 요소를 나타낸다. 여기서는 .edit-comment 클래스를 가진 요소가 클릭되었을 때를 나타낸다. .data("doc-id")는 선택된 요소의 doc-id라는 데이터 속성의 값을 가져옵니다. 이 값은 해당 댓글의 ID를 나타낸다. 따라서 이 코드는 클릭된 수정 버튼의 data-doc-id 속성 값을 가져와서 docId 변수에 저장한다.
.closest(".comment-item")은 클릭된 요소의 부모 요소 중에서 .comment-item 클래스를 가진 가장 가까운 요소를 선택한다.
.find(".comment-text")는 선택된 부모 요소 아래에서 .comment-text 클래스를 가진 요소를 선택한다.
.text()는 선택된 요소의 텍스트 내용을 가져옵니다. 따라서 이 코드는 현재 댓글의 텍스트를 가져와서 currentComment 변수에 저장한다.
// 사용자가 비밀번호를 입력한 경우
if (userPassword !== null) {
// Firebase에서 해당 댓글의 비밀번호를 가져옴
const docSnapshot = await getDoc(doc(db, "kdyComments", docId));
const commentData = docSnapshot.data();
사용자가 취소 버튼을 누르지않고 실제로 비밀번호를 입력한 경우에만 코드 블록을 실행한다.
docSnapshot은 Firebase Firestore에서 가져온 문서의 스냅샷(snapshot)을 나타낸다. 이 스냅샷에는 해당 문서의 데이터와 메타데이터가 포함되어 있다.
구체적으로 말하면, docSnapshot은 getDoc() 함수나 onSnapshot() 리스너 등에서 반환된 객체이다. 이 객체는 실제 문서의 데이터를 포함하고 있으며, 여기에는 해당 문서의 필드들과 그 값들이 포함되어 있다.
즉, 코드에서 docSnapshot.data()를 호출하면 해당 문서의 데이터를 가져올 수 있는데, 만약 문서가 존재하지 않는다면(docSnapshot이 null일 경우) data() 메소드는 null을 반환한다.
Firebase에서 해당 댓글의 데이터를 가져온다. getDoc() 함수는 비동기 함수이므로 await 키워드를 사용하여 비동기 작업이 완료될 때까지 기다린다.
가져온 데이터에서 댓글의 내용을 commentData 변수에 저장한다.
if (userPassword === commentData.password) {
const newComment = prompt("댓글을 수정하세요:", currentComment);
// 새로운 댓글이 입력된 경우
if (newComment !== null) {
// Firebase에서 해당 댓글 업데이트
try {
// 데이터 업데이트
await updateDoc(doc(db, "kdyComments", docId), {
comment: newComment,
});
alert('댓글이 수정되었습니다.');
showComments(); // 댓글 다시 로드
}
사용자가 입력한 비밀번호와 댓글의 비밀번호를 비교하여 일치하는지 하면 댓글을 수정할 수 있도록 해준다.
Firebase에서 해당 댓글의 데이터를 업데이트. 업데이트할 필드는 comment이고, 이를 newComment로 설정한다. 이 업데이트는 비동기 작업이므로 await를 사용하여 작업이 완료될 때까지 기다린다.
댓글을 다시 로드하여 화면에 업데이트된 댓글 목록을 표시한다.
catch (error) {
console.error("Error updating document: ", error);
}
}
} else {
// 비밀번호가 일치하지 않는 경우
alert("비밀번호가 일치하지 않습니다. 다시 시도해주세요.");
}
}
});
try 블록 에러를 처리하고 콘솔 창에 출력
$(document).on("click", ".delete-comment", async function () {
const docId = $(this).data("doc-id");
// 사용자로부터 비밀번호를 입력받음
const userPassword = prompt("댓글을 삭제하려면 비밀번호를 입력하세요:");
.delete-comment 클래스를 가진 요소를 클릭했을 때 이벤트를 처리하는 핸들러를 추가한다. 이때 async function을 사용하여 비동기적으로 동작하도록 한다.
클릭된 요소의 data-doc-id 속성 값을 가져와서 docId 변수에 할당한다. 이 값은 삭제할 댓글의 ID를 나타낸다.
// 사용자가 비밀번호를 입력한 경우
if (userPassword !== null) {
// Firebase에서 해당 댓글의 비밀번호를 가져옴
const docSnapshot = await getDoc(doc(db, "kdyComments", docId));
const commentData = docSnapshot.data();
사용자가 취소 버튼을 누르지 않고 실제로 비밀번호를 입력한 경우에만 코드 블록을 실행한다.
Firebase에서 해당 댓글의 데이터를 가져온다.
가져온 데이터에서 댓글의 내용을 commentData 변수에 저장한다.
// 입력한 비밀번호와 댓글의 비밀번호를 비교
if (userPassword === commentData.password) {
try {
// 비밀번호가 일치하는 경우, 댓글 삭제
await deleteDoc(doc(db, "kdyComments", docId));
alert('댓글이 삭제되었습니다.');
showComments(); // 댓글 다시 로드
} catch (error) {
console.error("Error deleting document: ", error);
}
} else {
// 비밀번호가 일치하지 않는 경우
alert("비밀번호가 일치하지 않습니다. 다시 시도해주세요.");
}
}
});
});
사용자가 입력한 비밀번호와 댓글의 비밀번호를 비교하여 일치 여부를 확인한다. 일치하는 경우와 일치하지 않는 경우를 각각 처리한다.
Firebase에서 해당 댓글을 삭제한다. deleteDoc() 함수를 비동기적으로 호출하여 댓글을 삭제하고, 작업이 완료될 때까지 기다린다.
댓글을 다시 로드하여 화면에 업데이트된 댓글 목록을 표시한다.
// 댓글을 출력하는 함수
async function showComments() {
$("#comments").empty(); // 이전 댓글을 지우고 새로 출력
const querySnapshot = await getDocs(collection(db, "kdyComments"));
querySnapshot.forEach((doc) => {
const data = doc.data();
const commentHtml = `
<div class="comment-item" style="background-color: white; border: 1px solid #11ddaa; border-radius: 0.375rem; padding: 0.5rem; ; outline: none; color: #1D886D; font-size: 18px; margin: 10px auto; display: flex; justify-content: space-between; align-items: center;">
<span class="comment-text">${data.nickname}: ${data.comment}</span>
<div>
<button class="edit-comment bg-[#1D886D] hover:[#2F9D27] text-white py-1 px-2 rounded-md focus:outline-none" data-doc-id="${doc.id}">수정</button>
<button class="delete-comment bg-[#1D886D] hover:[#2F9D27] text-white py-1 px-2 rounded-md focus:outline-none" data-doc-id="${doc.id}">삭제</button>
</div>
</div>
`;
$("#comments").append(commentHtml);
});
}
</script>
#comments라는 HTML 요소의 내용을 모두 지우기. 이 부분은 이전에 출력된 댓글을 모두 삭제하는 역할을 한다.
Firebase Firestore의 "kdyComments" 컬렉션에 있는 모든 문서를 가져오는 쿼리를 실행한다. getDocs() 함수를 비동기적으로 호출하여 쿼리의 결과를 기다린다.
쿼리 결과로부터 가져온 문서들에 대해 반복적으로 작업을 수행한다. 이 코드는 각 문서에 대해 지정된 함수를 실행한다.
현재 반복 중인 문서의 데이터를 data 변수에 저장한다. 이 데이터에는 해당 댓글의 필드들과 값들이 포함되어 있다.
각 댓글에 대한 HTML 코드를 생성한다. 댓글의 내용을 표시하고, 수정 및 삭제 버튼을 포함한다. 생성된 HTML 코드는 commentHtml 변수에 저장된다.
생성된 댓글 HTML을 #comments 요소에 추가한다. 이렇게 함으로써 화면에 댓글이 출력된다.
화살표 함수
querySnapshot.forEach((doc) => { ... });
이 코드에서 forEach() 메소드는 배열의 각 요소에 대해 지정된 함수를 실행할 때 화살표 함수가 사용되었는데, 이는 각 루프 반복에서 실행될 함수를 정의하기 위한 것이다. 각 요소에 대한 작업을 실행하는 함수를 화살표 함수로 정의했기 때문에 코드가 더 간결해지고 가독성이 좋음.
화살표 함수를 사용하면 함수 선언문을 사용하는 것보다 간단하게 함수를 작성할 수 있으며, 일반 함수와 달리 this 바인딩이 외부 스코프에서 상속되기 때문에 this가 의도한 대로 작동하는 경우가 많다.