솔미는 성장중
02_ (패스트캠퍼스X야놀자 프론트엔드 개발 부트캠프) 직원 관리 사이트 본문
프로젝트를 시작하며
패스트캠퍼스 X 야놀자 프론트엔드 부트캠프 1기를 참여하고 2번째 과제이다.
직원 사진 관리 페이지를 만들어보는 JS과제였다.
프로젝트 결과
(다른 사람 프로필은 건드리지 말아주시면 감사하겠습니다 (●'◡'●))
초점을 두고 준비했던 부분
필수 요구사항 만족하기 (firebase사용해 데이터 등록,수정,삭제 / CSS애니메이션 /
intersection observer 구현해보기
js를 이용해 애니메이션 만들어보기
Preview & 구현 내용
<메인 페이지 & 직원 리스트 >
- Firebase 를 활용한 사진 및 데이터 관리
- 프로필 페이지 제작
- 스크롤이 가능한 형태의 리스팅 페이지
- 반응형 페이지
- modal을 활용한 인재 등록 창 (사진 / 직책 / 깃헙주소 / 이메일 / 자기소개)
- header CSS 애니메이션
- 프로필 클릭 시 상세 페이지로 이동 (hash)
- tailwindcss를 활용한 css 관리 (반복적인 css 코드는 js로 구현)
< 상세 정보 페이지 >
- 상세 정보 확인 가능 페이지
- 사진 수정 및 삭제
- 텍스트 수정
- 인재 삭제
- details와 summary 태그를 활용한 버튼 설명
< 플레이그라운드 >
- 파티클 애니메이션 (gsap 사용)
- 클릭 시 색깔 변화
- 파티클 최적화 (lodash 라이브러리)
유저플로우
멘토님 코드리뷰 & 리팩토링
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처럼 상당한 오버헤드가 없는 순수 이진 바이트 배열이므로 처리하기가 더 빠르고 작다.
- 장점:
- 메모리 효율성:
- URL.createObjectURL()을 사용하면 파일이나 Blob을 메모리에 복사하지 않고도 URL로 참조할 수 있습니다. 이로 인해 메모리 사용량이 줄어들어 대용량 파일 또는 Blob을 다룰 때 유용합니다.
- 비동기 로딩 필요 없음:
- 일반적으로 웹 페이지에서 이미지나 비디오와 같은 리소스를 로드할 때는 비동기적으로 로딩해야 합니다. URL.createObjectURL()을 사용하면 이미지, 비디오 등의 리소스를 바로 렌더링할 수 있으며 비동기 로딩을 처리할 필요가 없습니다.
- 보안:
- URL.createObjectURL()은 웹 페이지 내의 리소스를 가리키는 URL을 생성하므로 웹 페이지 외부에서 해당 리소스에 직접 액세스하는 것이 어렵습니다. 이것은 보안 측면에서 장점으로 작용합니다.
- 메모리 효율성:
- 단점:
- 메모리 누수 가능성:
- URL.createObjectURL()을 사용한 후에도 해당 URL을 해제하지 않으면 메모리 누수가 발생할 수 있습니다. 따라서 사용 후에는 URL.revokeObjectURL()을 호출하여 URL을 해제해야 합니다.
- 메모리 관리를 직접 해줘야 하므로, 조금 더 복잡한 관리가 필요할 수 있습니다.
- 브라우저 호환성 고려 필요:
- 이 메소드는 오래된 브라우저에서는 지원하지 않을 수 있습니다.
- 메모리 누수 가능성:
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...)
- 파일 구조를 짜는게 쉽지 않았다.
'프로젝트' 카테고리의 다른 글
04_ (패스트캠퍼스X야놀자 프론트엔드 개발 부트캠프) 숙박 예약 사이트 *기업연계* (4) | 2023.12.19 |
---|---|
Lighthouse 점수 어떻게 올리는 건데~! (feat. 성능 최적화) (0) | 2023.12.15 |
proxy 서버 띄워 작업하기 (feat. vite) (1) | 2023.12.04 |
백엔드와 협업할 때 API를 못 받았다면? MSW mocking서버를 이용해보자! (feat. React & TypeScript) (0) | 2023.11.30 |
01_ (패스트캠퍼스X야놀자 프론트엔드 개발 부트캠프) Jacksonchameleon 웹사이트 클론 코딩 (0) | 2023.08.05 |