냉장고 파먹기(feat Javascript , react)

endmoseung·2022년 7월 31일
1
post-thumbnail

저번 회고에 이어 7월 29일 1차배포가 마무리 됐고 그동안 사용한 기술들에 대한 정리가 필요할것 같아서 벨로그를 켰다.

1 . 초기세팅

이부분에 대해서 vite로 해야할지 CRA로할지, 혹은 타입스크립트로 할지등 의견이 분분했는데 결국 CRA,리액트,자바스크립트로 하기로했다.
어차피 나도 타입스크립트에 대한 개념도 없었고, vite는 빌드속도에서 차이난다 정도만 알고 있었기 떄문이다.
거기에 더해 axios, react-router, recoil과같은 라이브러리를 썼다.

2 . 페이지 플로우

페이지 플로우는 메인페이지에서 위의 사진과같이 흘러가고 실제로는 로그인을 구현해서 서버내에 저장하려고 했는데 로그인까지 구현하기엔 시간이 부족하다 판단해 정보들을 recoil을 사용해 전역적으로 관리하기로 했다.
레시피나 재료들의 정보는 만개의레시피와 식약청에서 제공하는 API를 사용했다.

3 . 페이지 분배

우선 페이지단위로 태스크를 나누고 플로우상 유사한 데이터를 쓸것같은 페이지들은 묶어서 한사람이 개발하기로 했다.
프론트엔드는 총 3명이었고 원래 나는 검색결과페이지, 검색 상세페이지를 개발하기로 했었으나 검색페이지에서 어려움을 조금 느끼셔서 내가 검색페이지, 상세페이지를 개발했고, 다른페이지를 다른분들이 만드셨다.
그리고 공통적으로 사용할것같은 버튼들( ex)뒤로가기,재료버튼 )을 먼저 만들어서 다른 팀원들도 사용하게끔 했다.

4 . 내가 맡은 페이지들

우리페이지에서 메인기능이고 그만큼 구현에 애를먹고 첫 페이지구상때와는 다른 방향으로 간 기능들도 있었고 디자인도 많이 변경됐다.

4 . 1 검색 Page


우선 재료들에 대한 정보를 페이지가 mount됐을때 axios로 서버에서 데이터를 받아왔고 이를 useEffect를 이용하여 구현했다.

useEffect(() => {
    axios
      .get("https://nangpa-server.herokuapp.com/recipe/getIrdnt")
      .then((Response) => {
        let data = [];
        for (let i = 0; i < Response.data.length; i++) {
          data.push(Response.data[i].irdntNm);
        }
        setData(data);
        SetDataState(true);
      })
      .catch((Error) => {
        console.error(Error); //error타입으로
      });
  }, []);

이후 데이터를 Data라는 상태에 담아 관리했다. 처음엔 단지 데이터를 프론트에서 사용하기위해 관리했었는데 검색 혹은 냉장고에서 재료들을 추가했을때 사용자가 추가한 재료들을 다시 선택할수 없게하기위해 Data로 상태를 관리하는건 필수였다.
이전에 작업하던분이 react-filter-search라는 라이브러리로 검색을 구현하시고 가셨기에 검색하는 logic은 직접 작성하지 않았다.

위의 사진처럼 한글자라도 해당하는 단어가 있으면 검색목록을 보여주게끔 했고 이는 검색에 해당하는값들을 Map()을통해 렌더링했다.

<SearchFilter
            value={searchInput}
            data={searchInput ? data : []}
            renderResults={(results) => (
              <ListGroup className="search-list d-inline-flex justify-cotent-start align-items-center flex-wrap">
                {results.map((item) => (
                  <ListGroup.Item className="search-item d-inline-flex border border-0">
                    {/* TODO: Convert to Commont button components  */}
                    <Button
                      onClick={addListClick}
                      className="btn-item bg-transparent text-dark"
                    >
                      {item}
                    </Button>
                  </ListGroup.Item>
                ))}
              </ListGroup>
            )}
          />
        </SearchContainer>

하지만 여기서 내가 추가한 내용들을 다시 추가할수 없게하기 위해서는 사용자가 클릭했을때 경고문구를 띄워주거나, 추가한 내용 자체를 없애야할지 다른 팀원들과 협의하여 내용 자체를 없애기로 했다.
나도 이게 좋은 방법이라 생각했던 이유는 전자로 이런 issue를 다르게 될 경우 사용자가 한번더 클릭하고 중복된 값이라는 문구를 봐야하는 두가지의 issue가 더 생기기 떄문에 그런 경우자체를 없애는 방법인 후자로 하게됐다.
여기서 추가한 정보들(selectedIngredients)은 전역관리tool인 리코일로 관리했는데 그 이유는 이런 정보들이 검색결과페이지, 결과에서 나온 레시피의 상세페이지에서 필요했기떄문에 그렇게했다.

이제 이렇게 추가한 값들중 마음에 들지않아서 다시 뺄 경우가 있는데 이때 전역에 담긴 정보들(selectedIngredients)에서만 빼면 될거라 생각했는데, 이렇게만 처리할경우 검색했을떄 내가 뻈던 재료들은 다시 나오지않기 떄문에 리스트에서 뻄과 동시에 기존 검색 Data에 뺀 재료를 다시 추가할필요가 있었다.
이는 onClick이벤트중 outerText값을 이용해서 기존 Data값에 setData를 이용해 추가해줬다.

const handleDelete = (e) => {
    setSelectedIngredient(
      selectedIngredient.filter((item) => item !== e.target.outerText)
    );

    setData([...data, e.target.outerText]);
  }; 위의 setSelctedIngredient는 선택한 재료를 뺴기위해 눌렀을때 그 재료를 뺴주기위해 filter()를 사용했다.
  아래 setData가 기존 검색리스트에 다시추가해줬다. 

검색 페이지 사진에서는 냉장고페이지에서 추가한 재료들을 불러와 본인이 원한다면 추가할수 있는 기능이고 아래 사진과 같다.

기존 공간에 존재하던 텍스트들은 냉장고에 관한 데이터가 없을떄만 보여주고 있을때는 냉장고에 추가한 재료들만 보이게끔 해줬다. 이는 삼항연산자로 구현했다. 그리고 위에서 추가된 재료들은 검색해도 안보이는 모습.

{viewMyFrigeAtom.length === 0
              ? "하단의 냉장고 버튼을 눌러서 냉장고를 채워주세요"
              : viewMyFrigeAtom.map((item) => (
                  <FrigeButton handleAdd={handleAdd} item={item}></FrigeButton>
                ))}

위에서 말했던 냉장고에 관한 데이터는 실제로는 서버에 저장되어 사용자가 로그인했을때 다시 정보를 가지고있어야 했지만 로그인기능은 구현하지 못했기에 전역적으로 관리했고, 그 정보를 가져와 사용했다.

마지막으로 검색창 구현이었는데 기존엔 이미구현된 UI를 사용하려했으나

위와같이 너무 구려서 직접 만들기로 했다.
기존엔 돋보기가 보이고 실제 유저가 검색하기 시작했을때, 돋보기가 사라지며 우측에 X버튼이 활성화 되게했다. 아이콘들은 디자이너님이 주신 svg파일을 사용했고 이를 직접 사용하는 방법은 아래의 블로그를 참조했다.
https://velog.io/@juno7803/React-React%EC%97%90%EC%84%9C-SVG-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0

인풋안에 value를 searchInput이라는 상태로 관리했고 이 searchInput안에 값이있을때 돋보기버튼이 안보이게, 인풋창안의 패딩값을 원래대로 줄여주는 방법을 했고 X버튼에는 클릭이벤트를 주어 클릭했을때 값을 ""로 변경하게 만들었다.

const handleXButton = () => {
    setSearchInput("");
  };

4 . 2 검색결과 Page


우선 앞에서 받아온 재료들에 대한 상태를 서버로 Post해서 해당하는 재료가 하나라도 있는 레시피들을 받았다. 그리고 이 정보를 상태로 관리하여 아래 사진들이나, 텍스트, 좋아요수를 표현하는데 사용했다.

여기서 메인Title에서는 로그인이 구현됐다면 사용자의 이름을 넣는 방법을 하려했으나 그러지 못했고 선택한 재료는 앞서 검색페이지에서 전역적으로 관리한 정보들로 map()을 이용해 렌더링했다. 그리고 +버튼을 누르면 다시 검색페이지로가서 추가적으로 정보를 추가하게끔 했다.
여기서 다시 검색페이지로 가면 기존에 추가했던 재료들이 다시 검색리스트안에 들어갔는데 이는 시간이 부족해서 해결못했고 한다면 검색리스트를 전역적으로 관리하거나 이미 선택된 재료들이 있으면 필터링을 한번 더해주는 과정을 거치면 좋을거라 생각했다.

그리고 인기순과 정확도순으로 sorting되게끔 해서 사용자가 원하는 정보를 얻는 방식을 두가지를 통해 할수있게 했고 인기순은 좋아요순으로 sort, 정확도순은 백에서 레시피에 몇개의 재료가 해당하는지에 대한 정보를 뿌려줬는데 이를통해 sorting해줬다.
이부분을 처음에 얘기를 잘 못했기에 이는 후에 따로 백엔드분께 요청해서 해결했다.

인기순인지 정확순인지는 상태를 하나만들어 관리했고, sorting 로직은 아래와같다.

const handleByPopular = () => {
    setFoodData(
      foodData.sort((a, b) => {
        return Number(a.likeCnt) - Number(b.likeCnt);
      })
    );
    setByPopularState(true);
  };

  const handleByCorrect = () => {
    setFoodData(
      foodData.sort((a, b) => {
        return a.containCnt - b.containCnt;
      })
    );
    setByPopularState(false);
  };

처음엔 몰랐는데 위처럼하면 내림차순으로 sorting되는데 실제 정보들은 처음 클릭했을때 오른차순으로 더 정확한 정보들이 위에 나와야하기 때문에 b와 a의 위치를 바꿔줘야한다.

그리고 이제 실제 사진들을 클릭했을때 상세페이지로 넘어가게하는데 이때 도메인명을 사진에담긴 고유한 ID값으로 넘겨줬다.

const clickHistoryData = (e) => {
    navigate(`/${e.target.id}/detail`, { replace: false, state: e.target.id });
  };

state값으로 넘겨줘야 다음페이지에서 이값을 이용해 서버와 통신할거라 생각했는데 실제로 페이지를 담당하신분은 위에서 건네받은 도메인을 이용해 해결했다고 한다. 아마 그래서 이렇게 정보를 달라고 하셨던것같다 인프런처럼.

그리고 실제로 구현은 못했지만 페이지 우측상단에 필터링버튼이 있는데, 이는 네이버 쇼핑 필터처럼 구현하려했다.

해당하는 카테고리에 해당하는 재료를 선택하면 ex)비건,해산물 바로 필터링해서 제외한 레시피들로 리렌더링하려 했지만 시간이 없어서 미처 구현은 못했다.
그리고 팀원이 사용하신 부트스트랩에서 modal기능을 사용해서 아직 준비중이라는 코멘트를 띄워줬다.

4 . 3 네비게이션바

실제 휴대폰을 이용한 유저가 많을거라 생각했고 앱처럼 만들었는데, 그래서 모든페이지 하단바에 네비게이션바가 있다.
우선 클릭시 해당하는페이지로 라우터에 Link를 이용해 넘겨줬고, 해당하는 페이지에서 아래와 같이 우리서비스의 메인 색깔로 강조하려했다.

이렇게 하기위해 svg파일의 색상을 수정해야했고 이는 아래 블로그를 참조했다.
https://kyounghwan01.github.io/blog/React/handling-svg/#svg-%E1%84%89%E1%85%A2%E1%86%A8-%E1%84%8F%E1%85%B3%E1%84%80%E1%85%B5-%E1%84%87%E1%85%A1%E1%84%81%E1%85%AE%E1%84%80%E1%85%B5
이제 어떤 상황에서 색을 변경해줘야하나 고민하다가 전역적으로 현재페이지에 대한 정보를 관리해서 네비게이션바에서 해당하는 페이지의 색을 바꿔주면 어떨까?라는 생각이 들었고 모든페이지가 mount될떄 마다 이값을 본인페이지의 값으로 바꾸게 해주려했다.

하지만 시간이 부족해서 미처 다 구현못했고 이 방법이 맞는지 확인도 못해봤다.
그래서 후에 리팩토링할때 해야할것들에 추가해뒀다.

5 . 깃과 깃허브...

현업에서 어떤 플로우로 깃과 깃허브를 관리해야하는지 현업개발자 친구에게 물어봤고 이를 통해 많이 배우기도 했고, 깃과 깃허브가 얼마나 중요한지도 뼈저리게 느꼈다.
하지만 아직 깃과 깃허브에 대한 플로우만 대충 알 뿐, 실제로 내가 잘 알고 있는가? 에대한 질문엔 아직 대답할수 없다 느꼈기에 이번에는 적지 않고, 이제 깃과깃허브에 대해 공부해보고 경험해보며 다시 따로 한번 글을 작성해야겠다는 생각을 했다.

6 . 총평

이번에 그동안 써보려 했던 전역관리(Recoil)를 사용해봤고 백엔드와 통신하기위해 axios를 썼다.
Recoil을 처음 써봤기에, 무서웠지만 실제로 Recoil은 매우 친절했고 useState과 유사하게 사용해서 전혀 괴리감이 없었다.
그리고 실제로 어떤 정보들을 전역적으로 관리하면 좋을까에 대한 고찰을 하게 됐다.
그리고 다음에 프로젝트를 한다면 어떤 정보들은 전역적으로 처리해야할지 미리 선별해놓으면 좋을거라 생각했다.

내생에 처음으로 백엔드와 협업해본 프로젝트였는데, '아는만큼보인다'라는 말이 떠올랐다.
처음 생각할땐 백엔드에선 기본적인 정보만 받아와 모든 정보의 가공을 내가 해야할거라는 생각을 했었는데 실제로 받아올때 가공된 정보를 받아오는게 훨씬 낫다라는걸 경험했다.
그리고 우리 서비스는 한번에 받아오는 데이터들이 많지 않았기에 모두 받았지만 실제 서비스가 커지고 많은 데이터들이 생긴다면 사용자가 무작정 기다리지 않게 어떤식으로 정보를 받아오고 화면에 뿌려줄지도 고민을 해봤고, 이는 로딩창이나 스켈레톤 방식을 이용해서 하면 될것같다.


우리팀 백엔드분이 친절하게 대답해주셔서 도움이 많이됐다.

7 . 끝으로

이번에 작업을 리액트+자바스크립트로 했는데 백엔드와 같이 일해보니 타입스크립트를 배워서 실제 프로젝트를 만들면 더 좋을거라 생각했고(타입을 지정할수 있는점) 직접 이번프로젝트로 마이그레이션 해봐야겠다 생각했다.
그리고 미쳐 개발 못한 기능들, 오류가 있는 부분들은 수정해야 할거고 이부분은 우리팀에 뜻이 같은 분들과 차차 해결하기로 했다.
끝으로 테오가 남긴 회고글에서 너무 와닿는 말을 캡쳐했다.

https://naengpa.netlify.app/
냉장고 파먹기 많이 사랑해주세요~

profile
Walk with me

0개의 댓글