[패스트캠퍼스X야놀자: 프론트엔드 개발 부트캠프] JS과제-리팩토링

Bendeso·2023년 8월 28일
0
post-thumbnail

📌과제 내용

  • 직원들의 사진을 관리할 수 있는 사진 관리자 서비스를 만들어 보세요.

📝구현 내용

1. 리스트 페이지

img

  • table태그로 리스트 구현
  • 리스트 목록 hover시 구분가능하도록 리스트 색 변경

img

  • Debounce를 활용하여 검색 기능 구현

img

  • 리스트 목록 클릭 시 나오는 모달창 구현
  • 모달창과 버튼: 애니메이션으로 구현
  • 상세보기 버튼 클릭 시 클릭한 직원의 상세 페이지로 이동
  • 삭제하기 클릭 시 클릭한 직원 삭제

2. 직원 등록 페이지

img

  • 유효성 검사 코드 작성
  • 주소 API활용
  • 사진 선택 시 미리보기 구현
  • 사진 삭제 기능 구현
  • 등록 시 이미지는 AWS S3에 저장, 직원 정보는 로컬스토리지에 저장

3. 직원 상세 페이지

img

  • 직원 사진과 정보가 담긴 프로필 페이지 구현

4. 직원 수정 페이지

img

  • 직원 정보, 사진 수정 기능 구현

5. 모바일 반응형

img

  • @media를 사용하여 모바일 UI 구현

🙄 User Flow

img

🔎 리팩토링 작업

코드리뷰 1.

😫 Before

if (getItem) {
  getItem.forEach((item) => {
    createStaffList(item);
  });
} else {

  // data

  localStorage.setItem('infos', JSON.stringify(data));
  getItem = JSON.parse(localStorage.getItem('infos'));
  getItem.forEach((item) => {
    createStaffList(item);
  });

이러한 코드에서 멘토님께서 forEach코드가 중복되어 동일한 동작을 하므로 코드를 개선하는 게 좋겠다고 말씀해주셨다.
막상 코드를 작성할 땐 전혀 인지를 못했는데 지금보니 할말이 없다... (잠을 못자서 심신미약상태였다고 위안하자...)

위 코드는

😊 After

if (!getItem) {
  
  // data

  localStorage.setItem('infos', JSON.stringify(data));
  getItem = data;
}

getItem.forEach((item) => {
  createStaffList(item);
});

이처럼 중복코드를 없애고 깔끔하게 다시 리팩토링했다!
앞으로 중복코드에 더 심혈을 기울여야지...

코드리뷰 2.

😫 Before

tr.addEventListener('click', function () {
    const data = JSON.stringify(item);
    localStorage.setItem('lately-info', data);
    modal.classList.add('active');
    modalSelect.classList.add('active');
    
        if (selectCancelEventListener) selectCancel.removeEventListener('click', selectCancelEventListener);
    if (profileBtnEventListener) profileBtn.removeEventListener('click', profileBtnEventListener);
    if (deleteBtnEventListener) deleteBtn.removeEventListener('click', deleteBtnEventListener);
    if (yesBtnEventListener) yesBtn.removeEventListener('click', yesBtnEventListener);
    if (noBtnEventListener) noBtn.removeEventListener('click', noBtnEventListener);

    selectCancelEventListener = () => {
      modal.classList.remove('active');
    };

    profileBtnEventListener = () => {
      location.href = './profile.html';
    };

    deleteBtnEventListener = () => {
      modalSelect.classList.remove('active');
      modalConfirm.classList.add('active');
    };

    yesBtnEventListener = () => {
      getItem = getItem.filter((el) => el['id'] !== item['id']);
      const data = JSON.stringify(getItem);
      localStorage.setItem('infos', data);
      tr.remove();

      modalConfirm.classList.remove('active');
      modalDeleteSuccess.classList.add('active');

      setTimeout(() => {
        modalDeleteSuccess.classList.remove('active');
        modalSelect.classList.add('active');
        modal.classList.remove('active');
      }, 1000);
    };

    noBtnEventListener = () => {
      modalSelect.classList.add('active');
      modalConfirm.classList.remove('active');
      modal.classList.remove('active');
    };

    selectCancel.addEventListener('click', selectCancelEventListener);
    profileBtn.addEventListener('click', profileBtnEventListener);
    deleteBtn.addEventListener('click', deleteBtnEventListener);
    confirmCancel.addEventListener('click', noBtnEventListener);
    yesBtn.addEventListener('click', yesBtnEventListener);
    noBtn.addEventListener('click', noBtnEventListener);
  });

나는 tr태그를 생성할 때마다 tr태그에 클릭 이벤트리스너를 부여해 내가 누른 tr태그를 골라냈는데 멘토님께서 tr태그가 늘어날수록 이벤트리스너가 같이 늘어남에 따라 성능에 좋지않다는 말씀을 해주셨다.

지금 생각해보니 정말 비효율적으로 코드를 작성했다는 느낌을 받았다.
tr태그를 만들 때마다 너무 많은 이벤트가 생겨나고 이벤트 중복때문에 또 이벤트를 지우는 작업을 반복 해줘야하는 괴물같은 코드가 탄생해버린 것이다.

그러나 이벤트 위임을 적용하여 새 삶을 찾은 코드는 그럭저럭 봐줄만하다고 생각한다.

😊 After

// tr태그 클릭 시 동작 함수
function handleRowClick(event) {
  const tr = event.target.closest('tr');
  if (!tr) return;
  let data = getItem.find((item) => item.id === tr.id);
  data = JSON.stringify(data);
  localStorage.setItem('lately-info', data);
  modal.classList.add('active');
  modalSelect.classList.add('active');

  profileBtn.addEventListener('click', handleProfileBtnClick);
  deleteBtn.addEventListener('click', () => {
    handleDeleteBtnClick(tr);
  });
  selectCancel.addEventListener('click', handleNoBtnClick);
}

// 상세보기 버튼 클릭 시 동작 함수
function handleProfileBtnClick() {
  location.href = './profile.html';
}

// 삭제하기 버튼 클릭 시 동작 함수
function handleDeleteBtnClick(tr) {
  modalSelect.classList.remove('active');
  modalConfirm.classList.add('active');

  yesBtn.addEventListener('click', () => {
    handleYesBtnClick(tr);
  });
  noBtn.addEventListener('click', handleNoBtnClick);
  confirmCancel.addEventListener('click', handleNoBtnClick);
}

// Yes버튼 클릭 시 동작 함수
function handleYesBtnClick(tr) {
  getItem = getItem.filter((item) => item.id !== tr.id);
  const data = JSON.stringify(getItem);
  localStorage.setItem('infos', data);
  tr.remove();

  modalConfirm.classList.remove('active');
  modalDeleteSuccess.classList.add('active');

  setTimeout(() => {
    modalDeleteSuccess.classList.remove('active');
    modal.classList.remove('active');
  }, 1000);
}

// No 버튼 클릭 시 동작 함수
function handleNoBtnClick() {
  modalConfirm.classList.remove('active');
  modal.classList.remove('active');
}

tBody.addEventListener('click', handleRowClick);

위 코드는 이벤트 위임으로 tr태그를 아무리 만들어도 이벤트는 tbody태그 하나에만 적용돼있다.

코드리뷰 3.

코드를 살펴보면 같은 주소 문자열이 여러번 반복되므로 그러한 주소 문자열은 상수로 선언하여 관리하는 것이 좋다는 리뷰를 받았다.

.
.
.
아하!
(미처 생각하지 못한 부분!!)
앞으로는 주의하자😉

코드리뷰 4.

나는 상세페이지로 넘어갈 때 로컬스토리지에 값을 가져와서 그 값을 화면에 보여주도록 구현했는데 로컬스토리지에 값이 없을 경우에 대한 예외처리를 해주지 않았다.
이 문제는 try/catch문으로 예외처리까지 해주었다.

😫 Before

const data = JSON.parse(localStorage.getItem('lately-info'));


profileName.innerText = data.name;
profileEmail.innerText = data.email;
profilePhone.innerText = `${phone1}-${phone2}-${phone3}`;
profileAddress.innerText = data.address;
profileImage.src = data.imageUrl;

staffInfoEdit.addEventListener('click', function () {
  location.href = './write.html';
});

😊 After

let data;

try {
  data = JSON.parse(localStorage.getItem('lately-info'));

  profileName.innerText = data.name;
  profileEmail.innerText = data.email;
  profilePhone.innerText = FormatPhone(data['phone']);
  profileAddress.innerText = data.address;
  profileImage.src = data.imageUrl;
} catch (error) {
  alert('로컬 스토리지에서 데이터를 가져오는 동안 에러 발생: ' + error);
  location.href = './index.html';
}

staffInfoEdit.addEventListener('click', function () {
  location.href = './write.html';
});

추가로 상세페이지로 넘어왔을 때 로컬스토리지 안에 값이 없다면, 임의로 url을 조작하거나 로컬스토리지 값을 삭제한 것이기때문에 바로 index페이지로 추방시키도록 작성했다.

코드리뷰 5.

😫 Before

const phone1 = data['phone'].slice(0, 3);
const phone2 = data['phone'].slice(3, 7);
const phone3 = data['phone'].slice(7);

위와 같은 코드가 여러 페이지에서 나오는데 함수로 만들어서 재사용하시는 걸 추천해주셨다.
여러 페이지에서 사용하기 때문에 components폴더를 만들어서 FormatPhone.js파일로 컴포넌트화 시켜줬다.

😊 After

export default function FormatPhone(phoneNumber) {
  const phone1 = phoneNumber.slice(0, 3);
  const phone2 = phoneNumber.slice(3, 7);
  const phone3 = phoneNumber.slice(7);
  return `${phone1}-${phone2}-${phone3}`;
}

코드리뷰 6.

이번엔 자바스크립트킹왕짱제너럴갓엠퍼럴마스터조원분께서 코드리뷰를 남겨주셨다.
나는 유효성 검사 메시지를 success와 error 메시지 둘 다 html코드로 만들어 놓고 display: none 이나 display: block을 주는 방식으로 메시지를 보여줬다.

조원분께서는 하나의 메시지를 동적으로 관리하는 방법을 추천해주셨다.
사실 나도 하나의 메시지를 동적으로 관리하려는 생각을 했었다. (정말임.이제서야하는 얘기아님😐)
근데 왜 때문에 안했는지 나도 나를 이해못하는 중이다.

😫 Before

<div class="input-wrap">
          <label for="name">이름:</label>
          <input type="text" id="name" name="이름" placeholder="이름을 작성해주세요." required />
          <div id="name-error-message" class="error"></div>
          <div id="name-success-message" class="success"></div>
        </div>
// 이름 유효성 검사
function nameIsValid() {
  if (!write['name'].value) {
    errorMessage['name'].classList.add('active');
    successMessage['name'].classList.remove('active');
    errorMessage['name'].innerText = '이름을 작성해주세요.';
    isValid = false;
  } else {
    errorMessage['name'].classList.remove('active');
    successMessage['name'].classList.add('active');
    successMessage['name'].innerText = 'Success!';
  }
}

😊 After

<div class="input-wrap">
          <label for="name">이름:</label>
          <input type="text" id="name" name="이름" placeholder="이름을 작성해주세요." required />
          <div id="name-message" class="message"></div>
        </div>
// 이름 유효성 검사
function nameIsValid() {
  const nameMessage = Message['name'];

  if (!write['name'].value) {
    nameMessage.innerText = '이름을 작성해주세요.';
    nameMessage.style.color = 'red';
    isValid = false;
  } else {
    nameMessage.innerText = 'Success!';
    nameMessage.style.color = 'green';
  }
}

✋ 마무리하며

혼자 무언가를 만들어보거나 프로젝트를 할 때, 이렇게 코드를 작성 해도 되는건가?, 나 잘하고있는건가? 라는 질문이 계속 내 주위를 맴돌았었다. 그러나 봐줄사람이 없으니 당연히 이 질문에 답해줄 사람은 없었고, 그렇게 약간 찝찝한 기분으로 프로젝트를 끝내는 게 부지기수였다.

하지만 지금은 멘토님과 조원분들이 있기에 내가 어떤 부분을 비효율적으로 작성했는지, 여기선 어떤식으로 코드를 작성하면 좋은지에 대한 생각을 들을 수 있어서 혼자 공부할 때보다 확실히 배우고 얻어가는 것이 많다.

예전엔 제자리를 걷는 기분이라면 요즘은 성장하고있음을 몸소 느끼고있는 중이다.🤭

profile
성장을 위한 몸부림

0개의 댓글