class101 웹 사이트를 모티브로 이용자가 원하는 강의를 듣고, 또 직접 제작까지할 수 있는 사이트를 만들었다. 1차 프로젝트 때보다 뭔가 챌린지(..!)한 작업들을 해 보고 싶어서 일반 커머셜 사이트에는 없었던 클래스 생성하기 기능과, 모바일 반응형 웹을 구현하는 데 힘을 쏟았다!
2021.05.24 - 2021.06.04 (약 2주)
Backend: 신승호, 전현수, 최우석
Frontend: 김도은, 조원영(PM), 하연주
HTML/CSS, JavaScript, React, Recoil
🖍 메인 페이지
- 메인 이미지 carousel
- carousel을 활용한 클래스 미리보기
- 검색 및 local storage를 이용한 최근 검색어 저장
🖍 로그인
- 카카오톡 소셜 로그인
🖍 상품 리스트 페이지
- 후기 많은 순, 좋아요 순, 최신순 정렬 기능
- 페이지네이션
🖍 상세 페이지
- Q&A 글 및 댓글 작성 기능
- 찜하기(하트) 기능
🖍 클래스 생성하기
- File Reader API, Form Data API를 활용한 이미지 업로드 기능
- 양방향 input range custom
🖍 마이 페이지
- 쿠폰 및 최근 본 클래스, 찜한 클래스, 내가 만든 클래스 확인
🖍 모바일 반응형 레이아웃
- media query를 이용한 모바일 반응형 레이아웃 구현 (전체 페이지)
예전에 to-do list 만들기 할 때도 로컬스토리지를 이용했었는데... 그때는 그냥 따라했다면 지금은 기억을 되살려서 직접 고민&구현함!
🔍 검색 기록 객체를 담을 state 변수(이하 searchLog) 세팅
우선 검색 기록을 담기 위해 searchLog라는 이름으로 state 변수를 선언해 주었다. 초기값으로 만약 local storage에 저장된 기록이 있으면 그걸 getItem해오고, 없다면 빈 배열이 되도록 했다.
const [searchLog, setSearchLog] = useState( JSON.parse(localStorage.getItem('search log')) || [] );
나중에 addLog 메서드를 보면 알겠지만 searchLog에는 {id: (아이디값), word: (검색어)} 이렇게 생긴 객체를 넣어줌!
🔍 searchLog 렌더링하기
최근검색어를 보여줄 공간에서 map을 돌려준다. 각각의 객체를 log로 받아 log.id를 key로 부여하고, 검색어인 log.word가 htmlText로 나타나도록 했다.
<SearchTitle>최근 검색어</SearchTitle> {searchLog.map(log => ( <FlexDiv key={log.id}> {log.word} <XIcon onClick={e => { delLog(e, log); }} /> </FlexDiv> ))}
🔍 searchLog에 저장하는 코드
const addLog = e => { e.preventDefault(); setSearchLog([...searchLog, { id: Date.now(), word: e.target[0].value }]); e.target[0].value = ''; };
form의 onSubmit에 걸어 주는 메서드!
- 우선 e.preventDefault()로 submit의 기본 동작인 새로고침을 막고,
- setSearchLog()로 searchLog의 값을 다시 세팅해 준다. 정확히 말하면 전개연산자(...)+searchLog로 기존 값들을 불러오고, 새 오브젝트에 검색한 내용인 e.target[0].value를 담아 추가해 준다.
그냥 스트링이 아니라 오브젝트로 만든 이유는 나중에 삭제 시 참조할 id가 필요하기 때문이고, id는 고유하고 불변한 값이 뭐가 있을까 생각하다가.. Date.now()로 얻은 시간값을 이용했다.- 검색 후 input창을 비워주기 위해 추가한 e.target[0].value = ''
근데 쓰면서 보니... 어차피 검색하면 검색 결과를 보여주기 위해 클래스 리스트 페이지로 이동하기 때문에 1번, 3번은 굳이 필요 없을 듯! 끝도 없는 리팩토링.....
🔍 searchLog에서 삭제하는 코드
const delLog = (e, log) => { e.stopPropagation(); setSearchLog(searchLog.filter(logToDel => logToDel.id !== log.id)); };
이건 최근검색어 옆에 위치한 x아이콘(삭제 아이콘)에 걸어주는 메서드.
filter로 id를 비교해 삭제할 log를 걸러 주고, 거른 결과로 searchLog를 다시 세팅한다.
🔍 searchLog가 바뀔 때마다 local storage 저장값 바꿔주기
위의 addLog, delLog로 searchLog의 값은 바뀌지만 local storage에 저장된 값은 바뀌지 않는다! 그래서 useEffect를 componentDidUpdate 처럼 이용해서, 의존성 배열에 searchLog를 넣고 해당 값이 바뀔 때마다 local storage에 setItem해 준다. 참고로 JSON.stringify()는 local storage에 JS 객체를 바로 저장할 수 없기 때문에([objetc Object]라고 뜸) 필요한 것~~~
useEffect(() => { localStorage.setItem('search log', JSON.stringify(searchLog)); }, [searchLog]);
추가 예정.....🚶♀️🚶♀️🚶♀️ 메이비...
프로젝트만 해도 바쁜데... styled-component, hooks 등 새로운 개념들을 배우면서 그걸 바로바로 적용해나가야 했기 때문에 시간적 여유가 부족했다. (머 먹을 때는 여유만만이었지만....) 그리고 저번 프로젝트보다는 코드의 퀄리티와 리팩토링을 나름 신경쓰면서.. 했지만.. 또 막판에는 마음이 급해져서 아직 리팩토링할 거리들을 남겨 놓은 게 쬐끔 아쉽다. 무엇보다 제일 제일 아쉬운 건 좋은 사람들과 이제 떨어져야 한다는 점......ㅠ.ㅡ
그래도 잘한 점은 새로운 배움을 두려워하지 않았다는 것...! 처음 보는 API들도 많이 사용해 보고, 또 중간에 전역 상태관리가 필요해지는 바람에 어쩌지 어쩌지 하고 찾아보다가 Recoil이라는 신세계를 만난 것도 정말 정말 좋은 경험이었다. 덕분에 이제는 처음 보는 기술이 필요해져도 겁먹기보단 하면 되지!
하는 정신으로 뛰어들 수 있을 것 같다.
이번에도 깨달은 팀워크의 중요성..! 기간 내에 끝낼 수 있을까? 걱정했었는데 생각보다도 더 만족스러운 결과를 얻을 수 있었던 건 팀원들과의 ✨소통✨ 덕분인 것 같다! 1차 프로젝트 땐 백엔드분들께 뭔가 여쭤볼 때도 안 그래도 바쁘신데,, 계속 물어보면 귀찮으시지 않을까? 자꾸 이것저것 요청하면 번거로우시겠지? 하는 괜한.. 혼자만의 걱정이 쪼금 있었는데(사실 그래도 귀찮게 하긴 함><ㅎㅎ;;) 이번에는 2차인 만큼 다들 너무 친해지고 편하게 해 주신 덕에! 더 귀찮게 굴고 여러 번 되물으면서 틈날 때마다 맞춰 볼 수 있었다. 프론트 sister끼리도 서로 코드를 공유하고, 모르는 건 같이 머리를 맞대고 해결해나가면서 각자 자기가 맡은 부분뿐만 아니라 전반적인 흐름을 이해할 수 있었던 너무 좋은 경험이었다!!!!! 우리팀 너무너무 고맙고 최고>.<🧡
함께하고 싶은 개발자가 저의 목표인데,,💗 언젠가 도은님과 꼭 함께 해보고싶숨니당!!