IKEA 사이트를 모티브 한 가구 커머스 서비스
- React.js(v17)
- react-router-dom(v6)
- Sass
- JavaScript
- HTML5 / CSS
- Git
- GitHub
- Slack
- Trello
- Notion
이 서비스는 가구 커머스 서비스를 목표로 했습니다.
좋은 고객 경험을 줄 수 있는 기능 위주로 개발하려 했습니다.
저는 팀에서 회원가입, 메인, 로그인, 장바구니, 헤더, 푸터 페이지 등을 개발했습니다.
기능 구현 설명 펼치기
- 유효성 검사 📌
- 많은 input(text, checkbox, radio)정보들이 필요한데 유저가 각 유효성을 파악하기 쉽게 input창을 벗어 날 때와 회원가입 버튼을 눌렀을 때 유효성 검사를 실시해서 화면에 표시했습니다.
( input blur 시 유효성 검사 코드, 회원가입 버튼 클릭 시 유효성 검사 코드 )
- 유효성 검사는 이메일은 이메일 형식으로, 비밀번호는 최소 8자 이상, 대문자, 숫자, 특수문자가 포함돼있는지로 검사했고, 나머지 항목은 필수 항목일 경우 필수로 입력하게끔 유효성 검사를 실시 했습니다.
- state에서 숫자로 유효성 검사를 표현해줬습니다. (0: 유효성 검사 통과, 1: 필수 항목 미기입, 2: 이메일, 비밀번호 검사)
- 이메일과 비밀번호는 정규표현식으로 검사해줬습니다.
- 유저가 어느 부분에서 유효성 검사를 통과못했는지 파악하기 쉽게 useRef를 배열로 관리해서 유저가 유효성 검사 통과에 실패한 input창으로 화면이 focus되는 기능을 구현했고, 유효성 검사에 통과하지 못한 input이 여러개일 때 가장 위의 input으로 focus되게 했습니다.
( focus 코드 )
- useRef를 배열로 관리를 해서 focus 기능을 구현했습니다.
- ref 배열을 순서대로 검사해서 유효성 검사를 통과하지 못한 항목을 발견하면 focus되게끔 했습니다.
- 많은 input(text, checkbox, radio)정보들이 필요한데 유저가 각 유효성을 파악하기 쉽게 input창을 벗어 날 때와 회원가입 버튼을 눌렀을 때 유효성 검사를 실시해서 화면에 표시했습니다.
( input blur 시 유효성 검사 코드, 회원가입 버튼 클릭 시 유효성 검사 코드 )
- 추 후에 유저의 종류에 따라 다른 서비스를 제공할 수 있게 조건부 렌더링을 통해 두가지 유형의 회원가입을 구현했습니다. ( 코드 확인 )
- 유저가 비밀번호의 텍스트가 제대로 입력했는지 확인할 수 있게 비밀번호를 표시했다가 감출 수 있는 버튼의 기능을 구현했습니다. ( 코드 확인 )
- JavaScript의 classList toggle을 이용하여 비밀번호 표시 토글 버튼을 구현했습니다.
- 구현하고 관리할 수 있는 부분은 외부에 의존하지 않기위해 외부 페키지나 라이브러리를 사용하지 않고 체크박스를 구현했습니다. ( 전체 체크 코드, 개별 체크 코드 )
- 체크 박스들을 배열로 관리해서 체크 박스 기능을 구현했습니다.
- 이미지에 포인트 📌 ( 코드 확인 )
- 유저가 가구(상품)이 인테리어된 공간을 보면서 가구를 선택할 수 있는 기능을 state를 복합적으로 조합한 조건부 렌더링으로 구현했습니다.
- 서버에서는 구현이 따로 되지 않아서 mock data를 구조화 해서 진행했습니다.
- 마우스가 포인트 지점에 hover 될 때, 마우스가 포인트 지점에서 벗어날 때, 마우스가 전체 이미지에서 벗어날 때, 이렇게 세가지 상황에 맞춰서 기능을 구현했습니다.
3.3. Log in 페이지 ( 코드 확인 )
- 회원가입 페이지와 마찬가지로 유효성 검사를 실시 했습니다.
- 회원가입 페이지와 마찬가지로 비밀번호 표시 유무 번튼을 구현했습니다.
3.4. Cart 페이지 ( 코드 확인 )
- 유저에게 웹 어플리케이션으로서 장점을 경험할 수 있게 장바구니에서 유저가 주문 상품의 정보를 변경하면 화면은 프론트단에서 바로 변화 시키고 데이터는 서버로 보내줬습니다.
3.5. AsideBar ( 로그인 aside 코드, 카테고리 aside 코드 )
- 유저가 단순 웹 사이트가 아닌 웹 어플리케이션을 경험할 수 있도록 메뉴, 회원관리 버튼을 누르면 Aside Bar가 양쪽에서 나오는 기능을 외부 페키지나 라이브러리를 사용하지 않고 CSS만으로 구현했습니다.
- 단순 스타일링 작업
- 회원 정보에 따라서 차별화된 서비스를 제공하는 기능도 있으면 좋겠다는 마음으로 회원 가입 시 필요한 정보를 많이 담으려고 노력했습니다.
- 그러다 보니 관리해야하는 정보들이 많아 질 수 밖에 없었는데 이를 하나의 컴포넌트로 모든 input들을 다 처리해줄 수 있으면 좋을 수도 있겠다고 생각했습니다.
- check-box와 raido button들도 하나의 컴포넌트로 만들어주려고 했습니다.
- 항목들이 다양해짐(input, 연락수신 checkbox, 필수약관 checkbox)에 따라 각 항목들이 보여줘아하는 정보도 다양해지고 유효성 검사도 항목별로 각기 다르게 진행했어야했습니다.
- 각 input들의 name, placeholder을 통한 삼항조건연산자와 단축평가로 렌더링되는 상황과 각 스타일을 다르게 줬습니다.
- check-box와 radio button 컴포넌트의 경우는 type과 className(props), 하위 checkbox항목 유무에 따라 렌더링이 다르게 되도록 구현했습니다.
- 최종 결과 코드: Signup Input, Check Box & Radio button
- 하나의 컴포넌트(함수)가 모든 상황을 고려하게 하는 것이 얼마나 코드를 지저분하게 만들고 컴포넌트가 무거워질 수 있는지 뼈져리게 느꼈습니다. 거기에 따라 리팩터링을 계획했습니다.
- input가 다양해짐에 따라 종류마다 input에 보여줘야하는 정보도 다양해지고 유효성 검사도 각기 다른 문구를 띄워줘야하는 상황이기 때문에 일단 비슷한 역할을 하는 input들을 나눠서 컴포넌트를 나눠줘야할 것 같습니다.
- 필요하다면 Container 컴포넌트를 만들어서 나눈 각 input 컴포넌트들에 필요한 로직들을 각각 따로 모아서 관리할 것 같습니다.
- 지금은 컴포넌트들이 많이 안나눠져있어서 오히려 가독성이 떨어지는 것 같습니다. 그래서 더 잘게 컴포넌트를 나눠서 '여기서부터 여기까지가 어떤 역할을 하는 부분이다'라는 의미를 더 드러내고 싶습니다.
- 유저가 유효성 검사 결과를 자연스럽게 알아차릴 수 있도록 각 항목을 벗어날 때, 회원가입 form이 submit될 때 유효성 검사의 결과를 화면에 렌더링 해주고 '회원가입'을 submit할 때는 유효성 검사를 통과하지 못한 항목 중 최상단 항목에 focuc되게끔 기능을 구현하고 싶었습니다.
- 하나의 input 컴포넌트, 하나의 check box & radio button 컴포넌트로 모든 항목들을 관리하고 있어서 유효성과 관련된 state와 ref를 관리해주는 것이 까다로웠습니다.
- 유효성 검사는 세가지 경우에 대해서 검사를 실시했습니다. ( '필수 항목', 'email', '비밀번호' )
- 유효성 검사 결과는 숫자로 표현해줬습니다. 필수항목을 입력하지 않았을 경우 결과로 숫자 '1' email, 비밀번호 유효성 검사를 통과하지 못한 경우 숫자 '2'로 표현했습니다.
- 유효성 검사는 항목을 구분할 수 있는 값(name, className)에 따라 검사해서 유효성 검사 결과를 state로 관리했습니다. 그리고 결과의 숫자와 항목에 따라 결과를 렌더링했습니다.
- 먼저 각 input 항목을 벗어날 때(blur event) 유효성 검사를 했고, 분기문을 사용하여 각 유효성 검사 결과를 state에 담아주었습니다. ( 코드 확인 )
- check-box의 경우에는 필수 항목 유효성 검사만 진행하면됐습니다. -> 필수 check-box에 체크를 했다가 다시 풀어줄 경우(blur event) 검사를 실시했습니다. ( 코드 확인 )
- 회원가입 제출 버튼을 클릭하면 필수항목인데 입력하지 않았거나 체크하지 않았을 경우의 결과만 검사하면 됐습니다.
- 반복문과 유효성 검사 결과 state의 key값들을 통해 input value들의 state를 검사해줬고 결과를 유효성 검사 결과 state로 setState해줬습니다. ( 코드 확인 )
- 회원가입 제출 버튼을 클릭 시 유효성 검사를 통과하지 못한 항목 중 최상단 항목에 focus되게 하기위해 useRef를 배열로 관리하고 callback ref를 사용했습니다. ( 코드 확인 )
- 유효성 검사의 결과 숫자를 바로 jsx에서 문자로 변환해주다 보니 가독성이 많이 떨어지고 숫자가 무엇을 의미하는지 파악하기가 힘들었던 것 같습니다. -> 숫자로 검사결과를 담는 것이 아니라 바로 문자열로 화면에 보여주고 싶은 메시지를 유효성 결과 state에 담아주고 state를 화면에 보여주는 방식으로 리팩터링 하고 싶습니다.
- 회원가입 제출 함수가 유효성 검사, 포커스, 서버로 데이터 보내기 이렇게 세가지 일을 하는데 각 역할을 함수로 작성해줘서 코드가 어떤 역할을 하는지 명확하게 해주어서 가독성를 높이고 싶습니다.
- 이것 역시 input 컴포넌트를 적절히 나누면 유효성 검사 함수가 훨씬 간결해질 거 같습니다.
- 사용자가 가구를 고를 때 단순히 가구 이미지만 보고 가구를 선택하는 것이 아니라 인테리어가 이루어진 공간에서 가구를 보고 가구를 선택할 수 있게 하고 싶었습니다.
- 서버에서 api가 구현되지 않아서 mock 데이터를 직접 작성했어야했습니다.
- 좌표의 개수, 위치, 상품의 정보 등이 필요했고 프론트에서 단독으로 구현하는 것이 아닌 서버에서 이런 정보들이 모두 있어야한다고 판단했습니다. -> 데이터 구조 결정
- 각 인테리어에서 좌표들이 담긴 배열을 만든 후 배열을 jsx에서 화면에 렌더링하는 방식으로 구현했습니다.
- 인테리어 공간 이미지에서 모든 지점 정보를 보여주면 사용자 입장에서 가독성도 떨어지고 어느 가구의 정보인지 파악하는 것도 힘들겠다 생각했습니다.
- 그래서 각 좌표 지점들 중 처음에 기본값으로 한 지점의 정보를 보여주고 나머지 지점들은 마우스가 좌표에 올라 갔을 때 각 정보를 화면에 보여주는 방식으로 처리해주고 싶었습니다.
- 마우스가 아웃되면 다시 좌표에 해당하는 가구 정보를 보여주지 않고 싶었고 마우스가 인테리어 공간 이미지에서 벗어나면 기본값으로 돌아가서 처음에 보여준 지점의 정보를 다시 보여주고 싶었습니다.
- 앞서 말한 기능을 구현하려면 어떤 좌표가 처음에 기본값으로 보여주고자 하는 가구인지 서버에 저장돼있어야 판단해서 mock data에 default hover라는 정보를 각 좌표에 추가했습니다.
- 마우스가 각 좌표에 올라갔을 때, 마우스가 좌표에서 벗어날 때, 마우스가 인테리어 공간 이미지를 벗어날 때, 이렇게 세가지 경우에 대응할 수 있게 함수를 각각 만들었습니다.
- 각 함수는 분기문과 반복문을 이용해서 화면에 보일지 말지를 결정했습니다. (defaultHover값이 true일 때 화면에 보입니다.)
- defaultHover가 true일 때 'connectingDotHover' className을 줘서 visibility, opacity을 조정했습니다. ( CSS 코드, JSX 코드 )
-
각 좌표가 이미지에 찍혀있고 각 정보가 화면에 보이는 위치도 서버에서 줘야한다고 판단해서 mock data를 만들었는데 정보 위치는 프론트에서 정해주는 것이기 때문에 굳이 데이터에 들어가 있을 필요가 없을 거 같습니다.
-
마우스 이벤트 함수들이 map 메서드내부에 if문 내부에 for문 내부에 if-else문으로 이어지기 때문에 가독성이 많이 떨어지기 때문에 함수를 좀 더 간결하고 가독성을 높이는 작업을 할 것 같습니다.
리팩터링 예시
const hoverDot = (imgIndex, dotIndex) => { points[imgIndex].dots.forEach((dot, index) => { dot.hover = index === dotIndex; }); setPoints([...points]); };
- 체크박스를 스타일링해주고 싶은 상황이었는데 HTML에서 기본적으로 제공하는 체크박스는 CSS의 스타일링이 먹히지 않았습니다.
- 찾아보니 CSS로 스타일을 주는 것이 아니라 이미지를 가지고와서 구현하는 방법이 있긴했지만 HTML, CSS, JS만으로 해결해보고 싶었습니다.
- 진짜 checkbox는 opacity로 숨겨주고 checkbox아래에 div로 체크박스 모양을 만들어줬습니다.
- CSS를 이용해서 투명해진 checkbox가 클릭되면 div로 만든 checkbox가 체크되로록 구현했습니다. 코드 확인
- 회원가입 시 연락 수신 동의를 받는 체크박스를 구현하려고 했는데 하위 항목이 있는 체크박스이기 때문에 상위 체크박스를 클릭하면 하위 항목들이 모두 체크가 됐어야했습니다.
- 처음엔 단순하게 상위 항목에 state를 하나를 연결해서 state가 true가 되면 하위 항목들이 체크가 활성화되고 false가 되면 비활성화되게 구현했지만 더 복잡한 상황을 구현하려면 다른 방법이 필요했습니다. (다른 웹사이트 참고)
- 상위 항목 체크 -> 하위 항목 모두 활성화
- 상위 항목 체크 해제 -> 하위 항목 모두 비활성화
- 하위 항목 하나라도 체크 -> 상위 체크박스 활성화
- 하위 항목이 모두 비활성화 -> 상위 체크박스 비활성화
- 하위 항목 하나라도 체크 돼있을 때 상위 항목 체크 해제 -> 하위 항목 모두 비활성화
- 체크박스 항목들을 배열로 관리를 했습니다.
- 배열에 항목의 ID가 들어 있을 경우 체크가 활성화되는 식으로 구현했습니다.
- 상위 항목 ( 코드 확인 )
- 체크 해제 시 하위 항목이 하나라도 있으면 배열을 모두 비워줬습니다.
- 체크 활성화 시 모든 ID를 배열에 추가했습니다.
- 하위 항목 ( 코드 확인 )
- 하위 항목 중 하나라도 클릭이 되면 해당 하위 항목 ID와 상위 항목 ID를 배열에 추가했습니다.
- 반대로 하위 항목이 체크 해제되면 배열에서 뺐습니다.
-
이 부분 또한 모든 체크박스를 컴포넌트 하나로 관리하는 것에서부터 로직이 꼬였습니다. -> 컴포넌트 분리
-
현재의 코드는 다른 체크박스에는 적용을 시킬 수 없는 로직인데(항목이 추가되면 하드코딩 해줘야함) 좀 더 유연하게 적용할 수 있게 코드를 바꾸고 싶습니다.
-
배열로 관리하는 아이디어는 좋았다고 생각하지만 로직을 좀 더 간결하고 가독성 높게 구현할 수 있을 것 같습니다.
- 배열에 항목이 하나라도 있을 경우 -> 상위 항목 활성화
- 배열에 항목이 하나라도 있을 경우 상위 항목 비활성화 -> 배열 모두 비워줌
- 배열이 비워진 상태에서 상위 항목 활성화 -> 배열에 하위 항목 모두 추가
리팩터링 예시
-
일단 input의 check attribute를 상위 항목일 경우 빈배열인지 아닌지로 판별, 하위 항목일 경우 해당 id가 있는지로 판별해서 체크의 활성화, 비활성화를 구분해줄 거 같습니다.
-
const clickAllCheckbox = () => { if (familySignupContactChecks.length) { setFamilySignupContactChecks([]); return; } CONTACT_ALLOW_DATA.forEach((contactType, index) => { if (index === 0) return; familySignupContactChecks.push(contactType.id); }); setFamilySignupContactChecks([...familySignupContactChecks]); };
-
const clickSingleCheckbox = (checked, id) => { if (checked) { familySignupContactChecks.push(id); setFamilySignupContactChecks([...familySignupContactChecks]); return; } setFamilySignupContactChecks( familySignupContactChecks.filter(contactTypeId => contactTypeId !== id) ); };