1차 프로젝트 회고록

YunTrollpark·2022년 6월 6일
1

Project_2(CatCoDog)

목록 보기
1/1

벌써 프로젝트 시작이요...???

사전스터디를 시작했던게 4월인데 벌써 살갗따가운 6월이됐다. 저마다의 사연을 짊어지고, 그런 어색함에 가물어 있던 강의실 분위기가 언제 그랬냐는듯 비를 담뿍맞아 저마다의 사연에 결실을 맺어가고 있었다! 아직 잘 알지도 못하는데 프로젝트 시작이라고 해서 지레 겁부터 먹었지만, 같이 프로젝트를 진행하는 팀원분들과 동기분들 덕분에 무사히 마무리 할 수 있었다!

CatCoDog

1. 팀원 소개

• Front-end: 김철회, 윤경연, 남하임, 정재성, 안성주
• Back-end: 강세영, 박준형

2. 사용기술 스택

• Front-end: HTML, SCSS, React, Javascript
• Back-end: Python, Django, MySQL

3. 필수구현 기능

1) 회원가입 페이지

• Back-end DB에 기입한 정보 저장하기

2) 로그인 페이지

• 유효성 검사
• ID에 조건 걸기
• PW에 조건 걸기

3) 마이 페이지

• 회원 정보 보여주기
• 메인배너 슬라이드 라이브러리 없이 infinite loop로 구현
• 서브 슬라이드 라이브러리 없이 infinite loop로 구현
• 슬라이드 배너 클릭시 해당 제품 상세페이지로 넘어가기
• Nav 카테고리 클릭시 이동

5) 제품 카테고리

• fetch, jQuery등 사용해서 페이지 이동
• 더보기 버튼 누를 시 제품 더 보여주기

6) 제품 상세페이지

• 제품 상세페이지에서 장바구니 누르면 해당 제품 장바구니로 이동
• 댓글, 별점기능
• 수량 선택

7) 장바구니

• 제품 수량 수정
• 체크 박스
• 전체 삭제 및 개별 삭제
• 제품 구매 가격 하단에 합산해서 보여주기(얼마이상 담기면 택배비 제외 포함)

4. 내가 맡은 역할

메인페이지(Nav)

1) Dropdown menu bar

• 상수 데이터

const GLOBAL_NAV = [
  { id: 1, name: '베스트', list: [''] },
  {
    id: 2,
    name: '고양이',
    value: 'cat',
    list: [
      { id: 1, listname: '고양이 전체상품', value: 'cat' },
      { id: 2, listname: '통살', value: 'cat_meat' },
      { id: 3, listname: '동결건조', value: 'cat_frozen' },
      { id: 4, listname: '유산균/영양제', value: 'cat_supplment' },
      { id: 5, listname: '습식/파우치', value: 'cat_pouchmeal' },
    ],
  },
  {
    id: 3,
    name: '강아지',
    value: 'dog',
    list: [
      { id: 1, listname: '강아지 전체상품' },
      { id: 2, listname: '통살' },
      { id: 3, listname: '동결건조' },
      { id: 4, listname: '유산균/영양제' },
      { id: 5, listname: '저키/트릿' },
      { id: 6, listname: '습식/파우치' },
      { id: 7, listname: '껌' },
      { id: 8, listname: '수제간식' },
    ],
  },
  { id: 4, name: '용품', list: [''] },
  { id: 5, name: '대용량존', list: [''] },
];
export default GLOBAL_NAV;

: Dropdown 메뉴바를 만들기 위해서 만든 상수 데이터를 만들었습니다.
"강아지"라는 name의 key값에 접근했을때 그 하단 카테고리가 나와야 해서 하나의 객체 안에 배열을 넣고, 그리고 그 배열안에 하단 카테고리 하나하나를 객체로 만들어 줬습니다.
또한, 서브 카테고리가 없는 경우 list의 value에 ['']빈배열을 줘야 오류가 없었습니다.

• Js

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import GLOBAL_NAV from './globalNav';
import './NavBottom.scss';

const NavBottom = () => {
  const [currentId, setCurrentId] = useState();
  const navigate = useNavigate();

  const moveCategory = (e, value) => {
    if (e.target.className !== 'dropP') return;
    navigate(`list?category=${value}`);
  };

  const moveSubCategory = id => {
    navigate(`list?category=${id}`);
  };
  // 이벤트 버블링, 이벤트 위임

  return (
    <div className="navBottom">
      <ul className="navContainer">
        {GLOBAL_NAV.map(({ id, name, list, value }) => {
          return (
            <li
              className="dropList"
              key={id}
              onMouseEnter={() => setCurrentId(id)}
              onMouseLeave={() => setCurrentId()}
              onClick={e => moveCategory(e, value)}
            >
              <p className="dropP">{name}</p>

              {currentId === id && (
                <div className={`dropDownBoxContainer${id}`}>
                  {list.map(lists => {
                    return (
                      <div
                        className="dropDownBox"
                        key={lists.id}
                        onClick={() => {
                          moveSubCategory(lists.value);
                        }}
                      >
                        {lists.listname}
                      </div>
                    );
                  })}
                </div>
              )}
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default NavBottom;

: 메인 카테고리가 있고 하단에 서브카테고리가 있는 구조라서 메인카테고리에 먼저 map을 사용하고 그리고 그 안에 서브카테고리의 map을 한번 더 사용해서 완성을 했습니다.
그리고 서브 카테고리가 있는 경우 list에 배열안에 객체를 작성해서 각각의 객체에 id값을 줄 수 있는 구조로 완성했습니다.
그리고 현재 map이 두번 사용 됐기 때문에 링크로 이동할 경우 map을 사용한 모든 곳에 onClick 이벤트를 내려줬습니다.

onMouseEnter={() => setCurrentId(id)} // 해당 카테고리의 id 값이 선택 됐을 때 해당된 부분만 보여줌
onMouseLeave={() => setCurrentId()} // 클릭이 안되면 사라짐

2) 메인배너 슬라이드 라이브러리 없이 infinite loop

•Mock-data

[
  {
    "id": 1,
    "src": "/images/set5.png"
  },
  {
    "id": 2,
    "src": "/images/set2.png"
  },
  {
    "id": 3,
    "src": "/images/set8.png"
  },
  {
    "id": 4,
    "src": "/images/set4.png"
  },
  {
    "id": 5,
    "src": "/images/set7.png"
  }
]

: Mock - data에는 사진 이미지 소스와 각각의 id값을 주었습니다.

• Js

import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import slideBanner from '../../slideBanner.json';
import './RollingBanner.scss';

const RollingBanner = () => {
  const [slideIndex, setSlideIndex] = useState(1);
  const navigate = useNavigate();

  const moveProducts = id => {
    navigate(`/detail/${id}`); 
  };

  const bannerData = slideBanner; // Mock-data를 변수처럼 사용

  const moveSlide = index => {
    setSlideIndex(index);
  };

  useEffect(() => {
    const timer = setInterval(() => {
      if (slideIndex !== bannerData.length) {
        setSlideIndex(slideIndex + 1);
      } else if (slideIndex === bannerData.length) {
        setSlideIndex(1);
      }
    }, 2000);
    return () => clearInterval(timer);
  }, [slideIndex]);

  return (
    <div className="rollingBanner">
      <section className="rollingBannerContainer">
        {bannerData.map((img, index) => {
          const { id, src } = img;
          return (
            <div
              className={
                slideIndex === index + 1 ? 'slideImg active' : 'slideImg'
              }
              key={id}
            >
              <img
                src={src}
                alt="피드 이미지"
                onClick={() => moveProducts(id)}
              />
            </div>
          );
        })}
      </section>
      <div className="dotContainer">
        {Array.from({ length: 5 }).map((item, index) => (
          <div
            key={index}
            onClick={() => moveSlide(index + 1)}
            className={slideIndex === index + 1 ? 'dots active' : 'dots'}
          />
        ))}
      </div>
    </div>
  );
};

export default RollingBanner;

: 먼저 map을 사용할 때 필요한 Mock-data를 만들어 줬습니다. 그리고 index를 활용해서 사진을 하나하나 보여주도록 했습니다. useState를 사용해서 slideIndex의 상태를 업데이트 해줬습니다.
그리고 useEffect내부에서 timer라는 변수를 선언하고 조건을 걸어서 만약 slideIndex의 길이와 목데이터의 전체길이(이미지 5장)이 같지 않다면 slideIndex의 다음장으로 넘겨주고, 만약 slideIndex의 길이와 목데이터의 길이가 같아지면 다시 setSlideIndex를 1로 초기화 해서 반복을 돌도록 만들었습니다.
return에 clearInterval을 활용해서 timer를 초기화 해줬습니다.
그리고 useEffect 마지막 부분의 []에 slideIndex를 넣어줘서 해당 부분만 렌더링이 되도록 만들었습니다.

 className={
slideIndex === index + 1 ? 'slideImg active' : 'slideImg'
           }
// 이번 프로젝트를 하면서ClassName을 조건에 따라 여러가지로 다이나믹하게 선언해서 활용하는 방법을 배웠습니다.
 <div className="dotContainer">
        {Array.from({ length: 5 }).map((item, index) => ( //유사배열
          <div
            key={index}
            onClick={() => moveSlide(index + 1)}
            className={slideIndex === index + 1 ? 'dots active' : 'dots'}
          />
        ))}
  </div>
• Array.from() 메서드는 유사 배열 객체를 배열로 바꾸는데 자주 사용합니다.
• {length: 5} 해당 부분이 유사배열!
• key값으로 index를 받아서 onClick에 콜백함수로 위에서 선언한 moveSlide에 인자값으로 index + 1을 사용
• className에 조건을 걸어서 css 적용(slideIndex와 index + 1이 같으면 dots active 적용, false면 dots 적용

Array.from()이라는 새로운 메서드도 사용해봤는데 자세한 정보는 하단 링크!
https://velog.io/@cadyky95/Array.from

• scss

.rollingBanner {
  display: flex;
  position: relative;
  width: 100%;
  height: 924px;

  .rollingBannerContainer {
    width: 100%;
    height: 100%;
    position: absolute;
    background-color: #fafafa;

    .slideImg {
      position: absolute;
      width: 100%;
      height: 100%;
      opacity: 0;
      transition: 0.3s all ease-in;
    }

    .active {
      opacity: 1;
    }

    img {
      width: inherit;
      height: inherit;
      object-fit: cover;
      cursor: pointer;
    }
  }

  .dotContainer {
    position: absolute;
    display: flex;
    bottom: 50px;
    left: 50%;
    transform: translateX(-50%);
  }

  .dots {
    width: 8px;
    height: 8px;
    border-radius: 8px;
    margin: 0 10px;
    background-color: rgba(255, 255, 255, 0.3);
    transition: 0.3s all ease-in;
  }

  .active {
    width: 24px;
    height: 8px;
    background-color: rgba(20, 20, 20, 0.8);
  }
}

→ 사진은 position: absolute를 사용해서 겹쳐서 보여줌.
→ 여기서! 링크를 눌렀을때 해당 페이지로 이동하게 하려묜 z-index: 1을 사용해줘야함

3) 해당 배너 클릭시 제품 상세로 이동

  const navigate = useNavigate();

  const moveProducts = id => {
    navigate(`/detail/${id}`);
  };

useNavigate를 사용해서 Router에 설정된 주소에 user가 클릭한 id값에 따라 이동하는 부분을 만들어 주고

        <img
                src={src}
                alt="피드 이미지"
                onClick={() => moveProducts(id)}
              />

그리고 img에 onClick에 콜백함수를 사용하여 인자값을 id로 받는다고 설정해줬다.

5. Trello

소통! 소통! 소통! 디자인할때 항상 교수님이 "너네는 제발 현업 나가서 개발자랑 싸우지말고 소통하고, 협상하여 최선의 선택을 하라"라고 말씀하셨다. 그리고 그걸 이번 프로젝트를 하면서 정말 많이 깨닫게 되었다. 우리 조는 시작하기 전에 하단과 같이 약속을 했다.

• 욕심 내지 말기
• 좋게 좋게 말하기
• 모르면 모른다고 말하기
• 문제가 생기면 숨기지 말고 바로 말하기 

그래서 모르는 부분이 있으면 서로 본인의 일같이 도와주고, 시간이 없으면 다른 사람이 받아서 마저 완성하고 그렇게 서로가 돕고 도우면서 필수 구현도 완성할 수 있었습니다.

6. 개선점

"어떻게 첫 술에 배부를 수 있겠나!"
1) 이번 프로젝트를 하면서 백엔드와 소통을 많이 못해본 부분이 좀 아쉬웠습니다. 그래서 다음 프로젝트때는 백엔드와 통신을 적극적으로 해보려고 합니다.
2) state를 다음번에는 redux를 활용해서 관리를 해보고 싶습니다.
3) 상수 데이터에 html을 사용할 수 있다는 것을 처음 알게 됐씁니다. 그래서 다음에는 반복되는 데이터를 활용할 때 적극적으로 사용해보고 싶습니다.

7. 마침내!!! Finally!!! Endlich!!!

초반에는 Back-end와 소통하는 방법을 몰라서 고초를 겪었다. 해결이 안돼서 문제에 휩쓸려 갈때 다같이 버티며 해결책을 생각했고, 마침내 다같이 머리를 맞대어 해결책이 떠올랐을때 다시 거슬러가 수정을 하며, 역경을 견디는 다릿심을 키우지 않았나 생각합니다. 혹여, 비슷한 상황이 온다해도 지금의 경험들이 원동력이 되어 다시 한번 나아가게 해주지 않겠습니까!?ㅎㅎ
이 회고록을 작성하는 순간까지도 내가 잘 할 수 있을지 너무 불안하지만 이런 생각 조차 발판으로 삼아 다음 회고록때는 더 성장한 모습으로 작성하고 싶습니다!!!
그리고 항상 불안해 하던 저를 위로해주며 인내심을 가지고 지켜봐준 팀원들에게 다시 한번 감사의 말을 전합니다!

profile
코딩으로 세상에 이야기하는 개발자

1개의 댓글

comment-user-thumbnail
2022년 8월 21일

cadyky님 고생 많으셨습니다!! cadyky님이 있어서 Catcodog이 있을 수 있었습니다!!!
열정! 열정! 열정!!

답글 달기