본문 바로가기

TIL

TIL - firebase와 JS를 이용해서 방명록 구현

 

 

 

오늘 배운 것

  • firebase 기본 문법과 프라미스와 async, await (더 배울 필요가 있음)
  • git과 github 기초

 

 

 오늘은 내일배움캠프 스파르타코딩클럽 react_5기 첫 번째 날로 미니프로젝트로 간단한 "팀 소개 웹페이지" 과제를 

만들게 되었고 간단한 프로젝트지만 개발 분야에서 처음으로 하는 팀 프로젝트라서 역할 분담을 하는 데 쉽지 않았는데

.각자의 역할 분담을 하기 위해 와이어 프레임 단계에서 기능들을 혼자 였다면 안 했을 것들을 더 만들기로 계획해서 이런 것 또한 팀 프로젝트의 장점이라고 생각했다.

 

 

git과 github 기초

오전에 git과 github 기초에 대한 간단한 강의를 들었다. 

git이란 소프트웨어의 변경사항체계적으로 추적하고 통제하는 것으로 코드 변경점을 기록하는 도구고

github란 백업공유가 가능한 온라인 코드 저장소로 협업을 위해 사용하는 저장소라고 배웠다.

git 명령어는 init, add, commit, status, log, push, clone, pull을 배웠으며 다음 강의에서 branch를 배운다고 했다.

 

 

firebase 기본 문법과 프라미스와 async, await

 

오후에 역할 분담이 끝나고 팀 프로젝트에서 팀 소개 웹페이지에서 방명록 기능 구현 역할을 맡게 되었고 

사전캠프 기간에 들었던 강의를 토대로 firebase를 이용해서 방명록을 만들겠다고 계획을 세웠고 

 

html 코드는 아래와 같다.

<div id="guestbook">
        <h1 id="guestbook-section"><span>💬</span> 방명록</h1>
        <form id="guestbook-form">
            <label for="name"><strong>이름:</strong></label><br>
            <input type="text" id="name" name="name" required><br>
            <label for="message">메시지:</label><br>
            <textarea id="message" name="message" rows="4" required></textarea><br>
            <button type="submit">등록</button>
        </form>

        <div id="guestbook-entries">
            <!-- firebase로부터 받은 방명록 항목 표시 -->
        </div>
    </div>

 

 

처음에 firebase SDK 라이브러리를 가져올 때는 getDoc, addDoc 그리고 collection와 getFirestore를 가져왔지만

방명록의 기능중에 삭제와 순서대로 정렬되지 않는 어려움이 있었다.

 

 

// 방명록 폼 제출 이벤트 핸들러
        document.getElementById("guestbook-form").addEventListener("submit", async (event) => {
            event.preventDefault(); // 폼 제출 방지 --- (1)

            // 입력된 값 가져오기
            const name = document.getElementById("name").value;
            const message = document.getElementById("message").value;

            try {
                // Firestore에 데이터 추가
                await addDoc(collection(db, "guestbook"), {
                    name: name,
                    message: message,
                    timestamp: Number(new Date()) // 등록 순으로 정렬하기 위해 --- (2)
                });
                alert("등록 성공");
                // 페이지 새로고침
                window.location.reload();
            } catch (error) {
                // 오류 메시지 표시
                console.error("Error adding document: ", error);
                alert("방명록을 제출하는 동안 오류가 발생했습니다.");
            }
        });

(1)

처음에는 form에 required 속성이 있는 경우에는 빈 필드를 제출하려고 할 때 브라우저가 기본적으로 제출을 중단하고 오류를 표시하는 역할과 기본적으로 HTML 폼 제출의 기본 동작을 중단시키는 역할을 하는 event.preventDefault()을 중첩적으로 쓸 필요가 있나 고민 했었고 알아본 결과

event.preventDefault()를 사용하는 이유는 첫 번째 JavaScript를 사용하여 사용자의 입력을 검증하고 처리할 수 있다

이는 필수 입력 필드가 비어 있는지 확인하고, 필요에 따라 추가적인 유효성 검사를 수행할 수 있음을 의미한다.

두 번째로 필수 입력 필드가 HTML 속성인 required가 지정되어 있더라도, 사용자가 JavaScript를 사용하여 직접 DOM을 조작하여 필드를 비울 수 있어서 event.preventDefault()를 사용하면 이를 방지할 수 있다.

마지막으로 사용자 입력에 대한 추가적인 로직이 필요한 경우, JavaScript 이벤트 핸들러 내에서 처리할 수 있다.

그 결과 더 많은 유연성을 가질 수 있고 사용자 경험을 향상시키고, 데이터의 무결성을 보호하는 데 도움이 된다.

 

 

(2)

처음에는 

                await addDoc(collection(db, "guestbook"), {
                    name: name,
                    message: message,
                });

 

같은 형태 였는데 문제가 방명록의 순서가 사전순으로 정렬되어 나중에 등록되어도 이름 또는 메시지에 따라 맨 밑에 갈 수 있어서 firestore에 있는 orderBy 메서드와  timestamp를 이용해서 가장 최근에 등록한 메시지가 제일 위에 오도록 할 수 있었다.

 

 

 // Firebase에서 방명록 항목 가져오기
        async function fetchGuestbookEntries() {
            const querySnapshot = await getDocs(
                query(collection(db, "guestbook"), orderBy("timestamp", "desc"))
            );
            querySnapshot.forEach((doc) => {
                const entry = doc.data();
                displayGuestbookEntry(doc.id, entry); // 문서 ID도 전달
            });
        }

 

firebase를 사전 캠프 강의에서 배우면서 async, await 문법을 처음 보는 것은 아니지만 강의에서 자세하게 설명하지 않아서 프라미스와 async, await 문법에 대해 익숙하지 않았고 javascript.info에서 프라미스를 가수와 팬으로 비유해서 

  1. '제작 코드(producing code)'는 원격에서 스크립트를 불러오는 것 같은 시간이 걸리는 일을 합니다. 위 비유에선 '가수’가 제작 코드에 해당합니다.
  2. '소비 코드(consuming code)'는 '제작 코드’의 결과를 기다렸다가 이를 소비합니다. 이때 소비 주체(함수)는 여럿이 될 수 있습니다. 위 비유에서 소비 코드는 '팬’입니다.
  3. 프라미스(promise) 는 '제작 코드’와 '소비 코드’를 연결해 주는 특별한 자바스크립트 객체입니다. 위 비유에서 프라미스는 '구독 리스트’입니다. '프라미스’는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어 내는 '제작 코드’가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 해줍니다.

 

async는 funciton 앞에 붙고 해당 함수는 항상 프라미스를 반환하고 프라미스가 아닌 값을 반환 해도 이행 상태의 프라미스로 값을 감싸 이행된 프라미스로 반횐되도록 한다.

await는 뜻 그대로 프라미스가 처리될 때까지  기다린 다음 그 이후 반환 된다.

async, await 문법을 javascirpt.info에서 쉽게 설명 해줘서 이해가 가긴 했지만 아직 낯설어서 더 읽고 더 써야 할 것 같다. 

 

// 방명록 항목을 화면에 표시
        function displayGuestbookEntry(docId, entry) {
            const guestbookEntriesDiv = document.getElementById("guestbook-entries");
            const entryDiv = document.createElement("div");
            entryDiv.classList.add("guestbook-entry");
            entryDiv.innerHTML = `
                <p><strong>${entry.name}</strong>: ${entry.message}</p>
                <button class="delete-btn" data-doc-id="${docId}">삭제</button>
            `;
            guestbookEntriesDiv.appendChild(entryDiv);
        }
// 삭제 버튼 클릭 이벤트 처리
        document.addEventListener("click", async (event) => {
            if (event.target.classList.contains("delete-btn")) {
                const confirmDelete = window.confirm("정말로 이 항목을 삭제하시겠습니까?");
                if (confirmDelete) {
                    const docId = event.target.getAttribute("data-doc-id");
                    console.log("Deleting document with ID:", docId); // docId 출력
                    try {
                        // Firestore에서 문서 삭제
                        await deleteDoc(doc(db, "guestbook", docId));
                        // 삭제한 항목 제거
                        event.target.parentElement.remove();
                        console.log("Document deleted successfully."); //삭제 성공 메시지 출력
                    } catch (error) {
                        console.error("Error deleting document: ", error);
                        alert("방명록 항목을 삭제하는 동안 오류가 발생했습니다.");
                    }
                }
            }
        });

가장 애를 먹었던 부분인 삭제 기능이다. 처음에 삭제 버튼을 눌렀을 때 삭제가 잘 되는 것 처럼 보였지만 실제로

firebase에서 삭제가 되지 않았고 console.log로 디버깅을 해본 결과 삭제버튼의 data-doc-id 속성에 undefined가 할당되어서 entry 객체에 docId 속성이 존재하지 않아서 삭제버튼을 클릭해도 docId가 undefined로 설정되어 Firebase에서 해당 문서를 삭제할 수 없었던 것을 몰랐었다. displayGuestbookEntry 함수에서 docId를 함께 전달하여 삭제 버튼에 올바른 문서 ID가 할당되어 화면에도 삭제가 되고 실제 firebase에도 삭제가 되었다.

 

내일은 수정기능을 구현해봐야겠다.