솔미는 성장중

02_ (패스트캠퍼스X야놀자 프론트엔드 개발 부트캠프) 직원 관리 사이트 본문

프로젝트

02_ (패스트캠퍼스X야놀자 프론트엔드 개발 부트캠프) 직원 관리 사이트

solming 2023. 8. 29. 02:45
728x90

프로젝트를 시작하며

패스트캠퍼스 X 야놀자 프론트엔드 부트캠프 1기를 참여하고 2번째 과제이다.

직원 사진 관리 페이지를 만들어보는 JS과제였다.

 

 

프로젝트 결과

과제 결과물 (인재관리 사이트)

깃허브 주소

(다른 사람 프로필은 건드리지 말아주시면 감사하겠습니다 (●'◡'●))

 

 

 

초점을 두고 준비했던 부분

필수 요구사항 만족하기 (firebase사용해 데이터 등록,수정,삭제 / CSS애니메이션 /

intersection observer 구현해보기

js를 이용해 애니메이션 만들어보기


Preview & 구현 내용


<메인 페이지 & 직원 리스트 >

image

 

- Firebase 를 활용한 사진 및 데이터 관리

- 프로필 페이지 제작

- 스크롤이 가능한 형태의 리스팅 페이지

- 반응형 페이지

- modal을 활용한 인재 등록 창 (사진 / 직책 / 깃헙주소 / 이메일 / 자기소개)

- header CSS 애니메이션

- 프로필 클릭 시 상세 페이지로 이동 (hash)

- tailwindcss를 활용한 css 관리 (반복적인 css 코드는 js로 구현)

 



< 상세 정보 페이지 >

image

 

- 상세 정보 확인 가능 페이지

- 사진 수정 및 삭제

- 텍스트 수정

- 인재 삭제

- details와 summary 태그를 활용한 버튼 설명 




< 플레이그라운드 >

image

 

- 파티클 애니메이션 (gsap 사용)

- 클릭 시 색깔 변화

- 파티클 최적화 (lodash 라이브러리)

 


 

유저플로우

 

유저플로우 drawio


멘토님 코드리뷰 & 리팩토링

 

1. 

getElementById 과 querySelector 중 전자를 사용하는 것을 추천받았다.

각각 어떤 상황에서 추천되는지에 대해 더 깊게 알아보고자 했다.

  • 단순히 ID로 요소를 찾을 때
    getElementById를 사용하는 것이 빠르다!

  • 복잡한 선택 조건이 필요할 때
    클래스, 태그, 속성 등을 기반으로 더 복잡한 선택 조건을 사용해야 할 경우 querySelector를 사용하는 것이 유용함!
    ex) 선택자로 요소 선택하기 : 특정 클래스를 가진 첫 번째 요소를 선택하기
    : querySelector(".my-class")보다는 querySelector(".my-class:first-of-type")를 사용하는 것이 더 명확하고 정확

  • 크로스 브라우징 고려
    *
    : querySelector는 모든 최신 브라우저에서 지원되지만, 오래된 브라우저에서는 동작하지 않을 수도 있습니다. 이런 경우에는 getElementById가 더 안정적일 수 있다.

  • 특정한 선택자로 요소를 선택할 때
    : 예를 들어, 특정 클래스를 가진 첫 번째 요소를 선택하려면 querySelector(".my-class")보다는 querySelector(".my-class:first-of-type")를 사용하는 것이 더 명확하고 정확힘.

 

2.

(수정 전)

modal에서 이미지 선택 시 preview를 보여주는 코드에서 아래 코드를 사용했다.

const file = URL.createObjectURL(selectedFile);

(멘토님 코드리뷰)

이는 revokeObjectURL을 해주지 않으면 이미지 쌓이다 메모리 터지니 이미지 사라지는 시점에 revoke도 진행해주어야한다.

 

(수정 후 코드)

저장버튼을 누를 때 아래 코드도 함께 실행되도록 만들었다.

URL.revokeObjectURL(document.querySelector(".preview").src);

 

(추가적으로 알아본 점)

URL.createObjectURL()을 사용한 첫 사례라고 하셨는데, 다들 무엇을 썼는지 궁금해졌다.

 


URL.createObjectURL() 메소드:

Blob 객체를 url로 만들 때 사용하는 메소드.

Blob이란? js에서 이미지, 사운드, 비디오 같은 멀티 데이터를 다룰 때 사용한다.

 

Blob 자체로 유용하지 않다.  "BLOB는 단지 Binary Large OBject를 의미"

URL.createObjectURL()을 통해 바이너리 데이터를 Blob 객체로 캡슐화하여 사용할 때 진갈르 발휘한다!

Blob은 임시 파일과 같은 것을 제공하고 URL.createObjectURL()을 통해 해당 blob을 웹 서버의 파일인 것처럼 처리할 수 있다.

 

Blob을 사용하지 않고 또한 Base-64 로 인코딩된 문자열인 Data-URI를 사용할 수도 있지만, Data-URI의 문제점은 JavaScript에서 각 문자가 2바이트를 차지한다는 것. Blob은 Data-URI처럼 상당한 오버헤드가 없는 순수 이진 바이트 배열이므로 처리하기가 더 빠르고 작다.

 

  • 장점:
    1. 메모리 효율성:
      • URL.createObjectURL()을 사용하면 파일이나 Blob을 메모리에 복사하지 않고도 URL로 참조할 수 있습니다. 이로 인해 메모리 사용량이 줄어들어 대용량 파일 또는 Blob을 다룰 때 유용합니다.
    2. 비동기 로딩 필요 없음:
      • 일반적으로 웹 페이지에서 이미지나 비디오와 같은 리소스를 로드할 때는 비동기적으로 로딩해야 합니다. URL.createObjectURL()을 사용하면 이미지, 비디오 등의 리소스를 바로 렌더링할 수 있으며 비동기 로딩을 처리할 필요가 없습니다.
    3. 보안:
      • URL.createObjectURL()은 웹 페이지 내의 리소스를 가리키는 URL을 생성하므로 웹 페이지 외부에서 해당 리소스에 직접 액세스하는 것이 어렵습니다. 이것은 보안 측면에서 장점으로 작용합니다.
    •  
  • 단점:
    1. 메모리 누수 가능성:
      • URL.createObjectURL()을 사용한 후에도 해당 URL을 해제하지 않으면 메모리 누수가 발생할 수 있습니다. 따라서 사용 후에는 URL.revokeObjectURL()을 호출하여 URL을 해제해야 합니다.
      • 메모리 관리를 직접 해줘야 하므로, 조금 더 복잡한 관리가 필요할 수 있습니다.
    2. 브라우저 호환성 고려 필요:
      • 이 메소드는 오래된 브라우저에서는 지원하지 않을 수 있습니다. 

 


3.

(문제 상황)

3개의 페이지를 만들었는데 각 html 파일에 header를 각각 모두 작성해두었었다.

비슷한 코드를 중복해서 쓰는 것은 좋지 않기에 컴포넌트화해서 재활용하도록 했다.

 

(해결 방법)

headerComponent.js 파일 만들기 -> const headerTemplate을 만들고 그 안에 header속 들어갈 내용 작성(이때 페이지 이동 경로는 모두 절대 경로로 설정해주었다!) -> getElementById로 삽입할 위치 가져오기 -> innerHTML을 통해 template 삽입

 

4.

(수정 전)

Object.assign(profileContent,toSave);

 

(멘토님 코드리뷰)

Object.assign을 사용하시면 Immutable을 지키기 힘들어집니다.
객체의 복사에 대한 이해를 하시고 사용하시는 게 좋을 것 같습니다.

 

(추가 공부)

>  Object.assign()

하나 이상의 출처(source) 객체로부터 대상(target) 객체로 속성을 복사하고 대상 개체를 반환하는 메소드이다. 주의할 점은 원본이 변경된다는 점이다!

 

>  만약에 원본을 손상하지 않고 두 객체를 그냥 합치고 싶은거라면???

: assign쓸 때 맨 앞에 {} 빈 객체를 써주기

 

(수정 후 코드)

const profileContentWithUrl = Object.assign({}, profileContent, toSave);
await addDoc(collection(db, "profiles"), profileContentWithUrl);

 

5.

 

(멘토님 코드리뷰)

early exit 패턴을 사용해 가독성을 높일 수 있다.

Github은 올바른 URL을 담고 있는지, Email은 올바른 형식인지 추가로 validation을 진행해보시면 좋을 것 같습니다.

 

(추가공부)

early exit패턴이란? : (https://velog.io/@cabbage/early-exit-%ED%8C%A8%ED%84%B4)

early-exit 패턴은 어떤 로직에서 에러가 발생하는 경우를 가장 먼저 처리하는 패턴이다.
에러가 발생하는 경우, 코드를 더이상 실행하지 않고 바로 코드를 종료한다.

 

(수정 후 코드)

if (
  !(
    profileContent.name &&
    profileContent.position &&
    !profileContent.github.includes("github.com/") &&
    !profileContent.email.includes(".com") &&
    imgFileInput &&
    imgFileInput.files.length \> 0
  )
) {//유효성 검사를 통과하지 못한 경우}
else{
//유효성 검사를 통과함 경우
}

 

 

더보기

 

if (
  !(
    profileContent.name &&
    profileContent.position &&
    profileContent.github.includes("github.com/") &&
    profileContent.email.includes(".com") &&
    imgFileInput &&
    imgFileInput.files.length \> 0
  )
) {
  const githubInput \= document.querySelector(".profile\_\_github");
  const emailInput \= document.querySelector(".profile\_\_email");
  const modalLabel \= document.querySelector(".image label");
  const modalInputs \= document.querySelectorAll(
    ".input-profile-grid input"
  );

  if (imgFileInput.files.length <= 0) {
    modalLabel.style.backgroundColor \= "#ff692480";
  } else if (!profileContent.github.includes("github.com/")) {
    githubInput.value \= "";
    githubInput.style.backgroundColor \= "#ff692480";
    githubInput.focus();
  } else if (!profileContent.email.includes(".com")) {
    githubInput.style.backgroundColor \= "white";
    emailInput.value \= "";
    emailInput.style.backgroundColor \= "#ff692480";
    emailInput.focus();
  } else {
    emailInput.style.backgroundColor \= "white";
    modalInputs.forEach((item) \=> {
      if (item.value <= 0) {
        item.style.backgroundColor \= "#ff692480";
      }
    });
  }
} else {
  try {
    const imgFile \= imgFileInput.files\[0\];
    const storageRef \= ref(
      storage,
      "image/" + Timestamp.fromDate(new Date()) + imgFile.name
    );

    await uploadBytes(storageRef, imgFile);
    const url \= await getDownloadURL(storageRef);
    const toSave \= {
      image: url,
    };

    const profileContentWithUrl \= Object.assign({}, profileContent, toSave);
    await addDoc(collection(db, "profiles"), profileContentWithUrl);
    window.location.href \= "/";
    console.log("Document successfully written!");

  } catch {
    (error) \=> {
      console.error("Error writing document: ", error);
    };
  }
}
});

 

 


개선할 사항

  • intersection observer 구현하기
  • 검색 기능
  • 모듈화 하기

마무리하며

- 디자인 스케치 및 유저 플로우를 먼저 작성하고 구현해서 방향과 개발 순서를 파악하는 데에 용이했다.
- 기능은 구현했어도 효율적인 코드인지 firebase 문법에 익숙하지 않아 확신이 들지 않았다.
- 데이터를 db 및 스토리지에 올리고, 받아오고, 수정하고, 삭제하는 플로우를 파악할 수 있었다.
- 디테일한 부분에서 생각보다 손이 많이 갔다.
  : ex) 해쉬를 통해 페이지 이동 후 프로필 수정하고 새로고침 시에 계속 해쉬 주소에 맞는 화면으로 이동하는 상황. 다른 프로필을 수정해도 처음 접속했던 프로필로 이동하니 문제점이라고 인식. window.location.href를 통해 해쉬 주소로 연결되지 않도록 해주었다.
- 편집 기능을 구현할 때 어떤 메커니즘으로 사용자가 접하게 해야할 지에 대해 많은 고민을 했다.
- module로 분리하는 것, folder structure 관리 등이 쉽지 않았다. 이번 과제를 기회삼아 folder structure에 대해 깊이 있게 알아보고 다음 과제에 적용시켜 볼 예정이다.
- 다음에는 번들링하는 것을 시도해봐야겠다. (webpack, vite...)

- 파일 구조를 짜는게 쉽지 않았다.

728x90