2025년 1월 17일

김동환·2025년 1월 17일

📚 오늘의 공부(TIL): 주소 목록 페이지 구현


1. 프로젝트 개요

주소 목록을 불러와 화면에 표시하고, 메인 주소 설정 및 취소, 주소 수정 및 삭제를 관리하는 웹 페이지를 작성하였습니다. getAddress.htmlgetAddress.js 파일을 사용하여 클라이언트와 서버 간 통신을 처리하고, 데이터를 동적으로 렌더링하는 방법을 학습했습니다.


2. 사용된 기술

  • HTML: 주소 목록 화면의 뼈대를 구성.
  • CSS Framework: Pico.css를 사용하여 기본적인 스타일 적용.
  • JavaScript: Fetch API와 DOM 조작을 통해 동적인 기능을 구현.
  • REST API 통신: 서버에서 주소 데이터를 가져오고, CRUD 기능 구현.

3. 주요 기능

  1. 주소 목록 불러오기

    • fetchAddresses() 함수는 서버에서 GET api/users/me/addresses 엔드포인트를 호출하여 주소 데이터를 가져옵니다.
    • Authorization 헤더에 Bearer 토큰을 포함하여 사용자 인증을 처리합니다.
    • 서버 응답 상태를 확인하여 성공 시 데이터를 렌더링하고, 실패 시 오류 메시지를 표시합니다.
  2. 주소 목록 렌더링

    • renderAddressList() 함수는 가져온 주소 데이터를 기반으로 <ul> 요소에 동적으로 항목을 추가합니다.
    • 각 주소는 다음과 같은 항목을 포함:
      • 주소 이름과 상세 주소 표시
      • 메인 주소 여부에 따라 버튼 텍스트 변경:
        • 메인 주소 설정
        • 메인 주소 취소
      • 수정 및 삭제 버튼 추가
  3. 메인 주소 취소

    • .cancelMainButton 클래스가 추가된 버튼에 이벤트 리스너를 연결합니다.
    • 클릭 시 서버에 PATCH api/users/me/addresses/{addressId}/main 요청을 보내 mainAddressfalse로 설정합니다.
    • 요청 성공 후 페이지를 새로고침하여 변경사항 반영.
  4. 오류 처리

    • 통신 오류 또는 빈 데이터에 대한 적절한 예외 처리를 수행:
      • 오류 발생 시 사용자에게 오류 메시지 표시.
      • 등록된 주소가 없을 경우 "등록된 주소가 없습니다." 메시지 출력.

4. 코드의 흐름

  1. HTML 파일에서 JavaScript 파일(getAddress.js)을 defer 속성으로 로드하여 DOM 준비 후 스크립트 실행.
  2. 페이지 로드 시 fetchAddresses() 호출 → REST API에서 주소 데이터 가져오기.
  3. renderAddressList()에서 주소 목록을 동적으로 생성.
  4. 사용자 입력(메인 주소 취소 클릭 등)에 따라 추가적인 서버 통신 수행.

5. 배운 점

  • Fetch API 활용: 비동기 요청을 통해 REST API와 통신하는 방법을 익혔습니다.
  • DOM 조작: 데이터에 따라 동적으로 UI 요소를 생성 및 업데이트하는 로직을 작성하였습니다.
  • 에러 처리 중요성: 네트워크 통신이나 데이터 불일치 시 발생 가능한 오류에 대해 사용자 친화적인 메시지를 제공하도록 노력했습니다.
  • 토큰 인증 사용: localStorage에서 토큰을 가져와 헤더에 포함하여 인증을 구현하는 방법을 이해했습니다.

6. 추가 개선점

  • 사용자 경험 개선:
    • 버튼 클릭 시 로딩 상태 표시 및 중복 클릭 방지.
    • 오류 발생 시 상세한 원인을 사용자에게 제공.
  • 코드 재사용성 향상:
    • 메인 주소 취소와 설정 로직을 하나의 함수로 통합.
    • 반복되는 Fetch API 호출을 별도의 유틸리티 함수로 분리.
  • 보안:
    • localStorage에 저장된 토큰을 안전하게 관리하기 위한 대책 필요.

7. 결론

이번 작업을 통해 REST API를 활용한 데이터 처리 및 동적 UI 생성의 중요성을 체험할 수 있었습니다. 또한, 사용자 상호작용에 따른 서버 통신과 데이터 반영 과정의 흐름을 명확히 이해하는 데 큰 도움이 되었습니다.

getAddress.html

<!doctype html>
<html lang="en" data-theme="dark">
  <head>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
    />
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>주소 목록</title>
  </head>
  <body>
    <main class="container">
      <header>
        <h1>주소 목록</h1>
        <a href="createAddress.html" role="button">주소 등록</a>
        <a href="my-page.html" role="button">마이 페이지</a>
      </header>
      <ul id="addressList">
        <p>주소를 불러오는 중...</p>
      </ul>
    </main>
    <script src="js/getAddress.js" defer></script>
  </body>
</html>

getAddress.js

const addressList = document.querySelector('#addressList');

// 주소 목록 불러오기
const fetchAddresses = async () => {
  try {
    const response = await fetch('api/users/me/addresses', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
      },
    });

    if (!response.ok) {
      throw new Error('주소 목록을 불러오지 못했습니다.');
    }

    const addresses = await response.json();
    renderAddressList(addresses.data);
  } catch (error) {
    console.error('주소 목록 로드 오류:', error);
    addressList.innerHTML =
      '<p>주소 목록을 불러오는 중 오류가 발생했습니다.</p>';
  }
};

// 주소 목록 렌더링
const renderAddressList = (addresses) => {
  addressList.innerHTML = '';
  if (addresses.length === 0) {
    addressList.innerHTML = '<p>등록된 주소가 없습니다.</p>';
    return;
  }

  addresses.forEach((address) => {
    const listItem = document.createElement('li');
    listItem.innerHTML = `
      <strong>${address.addressName}</strong>: ${address.address}
      ${
        address.mainAddress
          ? `<span style="color: green;">[메인 주소]</span>
             <a href="#" class="cancelMainButton" data-id="${address.addressId}" role="button">메인 주소 취소</a>`
          : `<a href="mainAddress.html?id=${address.addressId}" role="button">메인 주소로 설정</a>`
      }
      <a href="patchAddress.html?id=${address.addressId}" role="button">수정</a>
      <a href="deleteAddress.html?id=${address.addressId}" role="button" class="deleteButton">삭제</a>
    `;
    addressList.appendChild(listItem);
  });

  // 메인 주소 취소 버튼 이벤트 추가
  document.querySelectorAll('.cancelMainButton').forEach((button) => {
    button.addEventListener('click', async (e) => {
      const addressId = e.target.dataset.id;

      try {
        const response = await fetch(
          `api/users/me/addresses/${addressId}/main`,
          {
            method: 'PATCH',
            headers: {
              Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({ mainAddress: false }), // 메인 주소 취소 요청
          },
        );

        if (!response.ok) throw new Error('메인 주소 취소에 실패했습니다.');

        alert('메인 주소가 취소되었습니다.');
        window.location.reload();
      } catch (error) {
        console.error('메인 주소 취소 오류:', error);
      }
    });
  });
};

// 페이지 로드 시 주소 목록 불러오기
fetchAddresses();
profile
Node.js 7기

0개의 댓글