본문 바로가기
Js&React 실습/React Prac

[React/main-project] 선인장 키우기

by sweesweet 2022. 10. 11.

팀 프로젝트

프론트2/ 백3

 

⏰개발 기간

22.09.08 ~ 22.10.07(계획 포함)

22.09.19 ~22.10.07(실제 개발 기간)

 

사용한 라이브러리(프레임워크)

TypeScript, React, styled-components, axios, react-router, Redux-toolkit, styled-reset

 

배포링크 

 

https://cactus-villeage.com

 

01234
배포 시연 화면

이번은 정말 다양한 경험을 했다. 내가 생각해낸 아이디어가 선정이 되고, 피그마로 화면을 첨부터 구성했고, 선인장 캐릭터를 그리고, 타입스크립트 리덕스툴킷을 처음으로 사용해봤다.메인에 나오는 선인장 내가 그린 것..! 하하하

 

내가 맡은 부분

페이지 기준 
로그인,메인페이지

페이지로 보면 참 적다ㅎㅎ 다만 메인페이지에서는 모든 기능이 모달 창으로 이루어져 있기 때문에  말만 페이지 두장이지 더 많은 기능을 했다고 자기위로 하구싶다

 

1.타입스크립트?

처음으로 해본 타입스크립트. 정말 많은 오류들을 만났고, 정말 오류를 고치는데 머리를 부여잡고 날밤을 깐 적도 있었다. 지금 생각해보면 별거아닌데..타입지정은 요로케 타입을 지정하라고 알려주니까 별상관은없었는데 이벤트함수일 때 쫌 힘들었다.

제일 힘들었던 부분은 e.target.value 이런걸할떄 value가 target에 없다거나,  'it possibly null' 이 에러.

이전에 개발할 때 만나보지 못했던 에러이지 않을까 싶다.

가장 힘들었던 에러는 이전 플젝때는  항상 버튼에 onClick으로  e.preventDefault()를 하고 ref로 데이터를 가져와 처리를 했었었다. 이번에는 onSubmit일때 데이터의 값을 받아오는 거로 하고 콘솔로그를 찍어 어떻게 생겼는지 확인후 평소와 같이 e.target[0]files[0]이런식으로 가져오려 했었는데 계속 EventTarget에 없다는 내용만 에러로 터졌다. 한 2시간 반동안, 이 타입을 찾고 말리라 하면서 스택오버 플로우에서 찾아보고 구글로 엄청나게 찾아봤는데 target대신 currentTarget을 써야하며(여기서도 엄청해맸다. value값이없다구나와서..), input태그에 id혹은 name이있다면 그거로 값을 받아올 수 있다고 했다.

e.target?.value <-옵셔널 체이닝도 이번 프로젝트때 처음 사용해봤다.

 

그래도 타입스크립트 덕분에 데이터 타입을 확인 할 수 있었고, 데이터 타입 때문에 발생할 수 있는 오류를 미연에 방지할 수 있어서 좋았다.

2.상태관리 라이브러리 변경?(추가)

프리 프로젝트때 zustand를 썼는데, 이번 프로젝트 때는  리덕스 툴킷을 사용했다. 

>변경 이유 

zustand를 사용했었는데 정말 편한 게 맞다.  리덕스에 비해 보일러플레이트 코드가 없기 때문에 정말 편한 게 맞는데 최신 라이브러리라서 레퍼런스가 많이 없었다. 얼마나 없었냐면 영어자막도 없는 영어 유튜브를 볼 수 밖에 없었다.(지금은 패캠에 강의 팔음)

이게 내가 사용하는 방법이 맞는 건가를 매번 의심하게 되고, 매번 zustand  깃허브에가서 사용법을 읽어도 매번 의심이 갔다.  팀원과 메인때는 다른 상태관리 라이브러리를 사용해보자 합의 하고, 보일러플레이트 코드가 비교적 적은 리덕스 툴킷을 사용했다.

 

>상태관리 라이브러리 사용법에 대한 편견

props drilling 때문에 사용한다고 배웠기 때문에 depth가 깊은 곳에서만 사용이 가능할 거라고 생각했다. 사실 툴킷을 사용하자고 마음 먹기 전에  깊이가 그렇게 깊지 않은데 굳이 상태관리 라이브러리를 사용할 필요가 있을까? 싶기도 했다.  처음에는 로그인 후 유저의 데이터를 받고 저장하는 용도로만 사용했다. 전역으로 사용할 수 있는 이 데이터 밖에 없다고 생각했으니까.  하지만 멘토님의 이야기를 듣고,  depth가 깊은 곳에서만 사용한다는 편견을 버릴 수 있었다. 툴킷으로 모달을 관리한 부분은 아래에 나온다

 

 

 

3. 모달을 어떻게 구현했나?

프로젝트 초반에 정말 많은 고민을 했었다. 어떻게 이 많은 모달을 구현하지? (모달의 갯수를 세보니 총7개였닿ㅎ) 이 이벤트 함수들은 어떻게 해야하는거지? 모달을 그냥 appjs 최상위에 구현하기엔 뭔가 z-index도 신경써야하고  어떻게 해야하는걸까 컴포넌트들을 재활용하는게 최고일것같은데 매번 다르게 어떻게 넣지? 등등 많은 고민을 하면서 머리를 부여잡고 있었던 것 같다.

(폼의종류, 폼 사진업로드/미리보기 는 생략)

아래는 해당 내용에 대한 기술 발표 내용이다.



1. 모달 구현 방법

이전에 모달 구현했던 개인 프로젝트 때의 방식
app.tsx나 메인 페이지 안에서 useState로 각 모달의 상태를 관리하는 방식

>
문제점

대부분의 기능이 모달 창에서 이루어지고 있고 모달마다 실행되는 함수가 다르기 때문에 고려할 사항이 많아짐

>이번 프로젝트에서 구현한 방식
react의 포탈
portal을 사용하게 되면, 외부에 존재하는 모달 DOM을 React App DOM에 존재하는 것처럼 화면에서 보여질 수 있기 때문에, 모달이 열고 닫힐 때 발생할 수 있는 다른 부분을 고려하지 않고 여러 개의 모달을 포탈을 사용하여 구현

2. 모달 내 컴포넌트 / 함수 처리

>이전의 프리 프로젝트 때의 방식
컴포넌트 내에 함수 존재(분리 x) 같은 컴포넌트를 쓸 시에 props에 따라 조건부 렌더링& 삼항 연산자로 분리
https://jessiecom.tistory.com/108 <-정말 지옥의 삼항연산자

>문제점
코드의 가독성 저하, 복잡도 ↗

>이번 프로젝트에서 구현한 방식
컴포넌트 내에 존재하던 함수들을 따로 분리해 관리 -알림 모달: 한 컴포넌트로 status가 '포기,탈퇴,로그아웃' 중 어떤 상태 인지를 함수인자로 받아, 실행되는 함수와 출력 되는 메세지만 변경해서 사용 -일일 챌린지: 챌린지 각각의 폼이 다르기 때문에 각 폼에 맞는 컴포넌트를 만든 후, 해당 컴포넌트에서 함수를 실행하게 하고,상태에 따라 알림 모달과 동일한 방법으로 사용

3. 모달의 상태 관리

>프로젝트 초기 방식
각각의 버튼 컴포넌트에서 useState를 사용해 모달의 열림/닫힘의 상태를 변경하는 Setstate를 status 와 함께 Props를 내려 관리

>문제점
폼의 제출 여부에 따라 혹은 로그인 여부에 따라 창이 닫히도록 useEffect를 사용하여 실행되도록 했지만 창이 제대로 열리지 않는 sideEffect가 발생 제대로 작동하게 만들기 위해서는 여러 개의 조건문 필요

>최종으로 구현한 방식
리덕스 툴킷 : 모달의 열림/닫힘, 지금 모달의 상태 즉 어떤 종류의 모달을 사용하고 있는 지를 저장해 관리(일일+챌린지 제출폼 모달 /알림 모달 따로 관리)

 

4. 로그인 유지& 로그인 안했을 시 main페이지 mypage 접근 금지

로그인 유지를 위해서 새로고침 했을 시 토큰 재발급을 하고 그 후 다시 개인정보를 받아오고, 토큰이 만료되는 기간인 30분 전에  다시 재발급- 개인 정보 받기를 해야 했다. 꾸역꾸역 다 만들고 나니, sideEffect가 발생했다

 

>발생한 문제 -새로고침시 redux 기본값 false->로그인페이지로 이동->리이슈통신 완료 후 빠른 속도로 다시 메인페이지로 이동

로그인이 안되어있으면 main에서 인트로로 돌려보내게 해놨었는데

useEffect로 걸어놓은게 통신 속도보다 빨라서 리이슈(토큰재발급)후에  마치 버벅이듯이 보이게되고, 다시 돌아오더라도 아래 nav바도 사라지게 되는 오류가 발생

>해결방안

이 오류를 변경하기 위해서 ProtectRoute를 생성. loginStatus가 true일때만 main으로 이동하게 시켰다

Routes부분       
        <Route
          path="main"
          element={
            <ProtectedRoute isLoggedIn={isLoggedIn}>
              {status === 'none'
                ? (
                <Pages.MainNoCactus />
                  )
                : (
                <Pages.MainCactus />
                  )}
            </ProtectedRoute>
          }
        />

 

const ProtectedRoute = (props: ProtectedRouteProps) => {
  const { children, isLoggedIn } = props;
  const dispatch = useDispatch();
  if (!isLoggedIn) {
    dispatch(redirectLogin());
    return <Navigate to="/login" />;
  }

  return children;
};
export default ProtectedRoute;

 

>발생한 문제 

이걸 해도 화면상 버벅이는 문제는 계속 발생했다. 그 전과 차이점이 없었음...

 

>해결방안

새로고침시 랜더링의 문제라면  pending을 걸어버리자..! App.tsx에 useState를 이용해 reissue 통신이 성공했을 때만(status에 상관x) pending을 false로 주었다. 그랬더니 로딩중 화면이 뜨면서 버벅이는 현상 또한 없어졌다. 

 

>발생한 문제

다 해결되고 나니 다른 문제가 보였다. 리이슈가 필요하지 않은 화면에서도 리이슈가 일어나서 계속 로그인 화면으로 돌아가게 됐다.

 

>해결방안

새로고침 했을 시 path를 가져와서 (리이슈에)해당하지 않는 path일 시 return;

//리이슈코드
const path = window.location.pathname;
  if (
    path === '/' ||
    path === '/login' ||
    path === '/signup' ||
    path === '/forgotpw'
  ) {
    setIsPending(false);
    return;
  }
  const { status } = await getReissue();
  if (status < 300) {
    setTimeout(() => {
      void reissue(dispatch, navigate, setIsPending);
    }, 950 * 60 * 30);

    const { status, data } = await getMe();
    if (status < 300) {
      dispatch(loginUser(data.data));
      setIsPending(false);
    }
  } else {
    setIsPending(false);
    dispatch(logoutUser());
    navigate('/login');

  }

+다른방법은 없는 걸까?

5. 아쉬운 점 

> 코드 리팩토링

폴더 분기를 어떻게 해야할 지 감이 안잡혔다. 따라하고는 싶지만 페이지는 하나인데 모달때문에 컴포넌트는 20개에 가깝고, 그렇다고 ui로 나누기엔 뭔가 애매했다. 그리고 함수들은 어디에다가 놔야하는 것일까 각 페이지 별 훅같은 경우는 어디다 두는게 나을까 정말 많은 고민을했지만 폴더를 잘 나누지도, 좋은 코드를 짠 것 또한 아닌 것 같다. 

코드의 재사용성을 고려하면서 개발을 했지만 지금 생각해보면 조금 더 잘했으면 좋았을 것 같다.  프리프로젝트 보다는 가독성이나 재사용성이나 더 나았다고 생각한다. 하지만 로그인했을 때 메인페이지// 안했을 때 메인페이지 이렇게 두개로 나눠서 했는데 버튼 자체를 disable하면 되었을 텐데. 불필요한 페이지가 아니였나 싶다.

 

>프롭스는 대체 뭘 내려야할까?

훅을 만들다가 그냥 컴포넌트에서 dispatch나 setIsPending 이런걸 프롭스로 내려버렸다. 내려도 되는 것일까? 정도를 알고 싶다!

 

>성능 최적화

icon을 받을 때 cdn을 사용해서 그런지, 성능을 확인했을 때 그다지 좋은 점수를 받지 못했다. 차라리 라이브러리를 사용했더라면 더 좋은 점수를 받았을 것 같다.

 

> 폼 버튼 딜레이 처리

 폼을 submit할 때 연속적으로 누르게 된다면, 한번만 리퀘스트가 가지는게 아니고 여러번 가게 돼서 오류를 초래할 수 있다. 

폼버튼을 누르고 연속 클릭을 막았어야했다. throttle을 구현했어야 했는데 추후에 실습해 봐야겠다.

 

>부족한 설명

우선 귀여운 ui로 사람을 꼬셔보자 싶어서 엄청 귀엽게 만들려고 노력했다. 다 만들고 생각해보니 물방울 표시든 햇빛 표시든 유저들은 이게 뭔지 모른다는 거였다. 만드는 사람만 안다면 그게 무슨 좋은 홈페이지일까 싶었다. 처음 오는 유저라면 우선 마구 클릭하다가 화가 나서 나가버리겠지? 그래서 프로젝트가 끝나고도 계속 생각이 나서 처음 유저가 모르면 설명을 눌러볼 수 있게 물음표 버튼을 만들었다. 

원래 물음표 버튼을 만드려고 했을 때 고민이 많았다. 이걸 만들까 말까 고민을 하다가 

오랜만에 친구를 만났고 만든걸 보여주자 친구가 그 얘기를 했다.

"난 요즘 아이콘만 있으면 불편하더라. 어쩌라는 건지 모르겠어. 아이콘보다 정직하게 기능을 적어줬으면 좋겠어."

이 얘기를 듣고 좀 많이 반성했다. 버튼 아래에 작은 글씨로라도 버튼에 대한 기능이 적혀져 있었음 좋았을텐데 햇빛버튼아래 등록이라고 적어만 놨어도 ux적으로 지금보다 더 낫지 않았을까 싶다.

사용자의 관점에서 생각하고 더 나은 ux를 위해 개발하겠다는 나는 어디로간걸까? 예쁘면 장땡이아니다. 예쁘고 쓸만해야 장땡이다.

+ 바로 로그인으로 연결되는 것도 나쁜 ux다. 체험을 할 수 있도록 만들었어야 했다...다음에 다른 플젝을 한다면 꼭 그렇게 만들어야겠다. 그래도 랜더링할 때 버벅이는거 고쳐서다 행이다

 

6. 후기

계획짜는 걸 포함한 길다면 길고 짧다면 짧다고 할 수 있는 5주였다. 처음 계획부터 쉬웠던 게 하나도 없었다.  정말 수십 개의 아이디어를 내고 몇 번을 갈아 엎었다. 다들 하나씩은 하는 기존의 CRUD 인 게시판을 벗어나고 싶어서 팀원 전원이 발버둥을 치며 아이디어를 치열하게 내고 치열하게 내치면서 계획을 짰었다. 그 이후 부터는 주말을 반납하면서 정말 열심히 개발했다. 매일 게더에 모였고, 고민고민하다가 안되면 멘토님께 물어보고, 또 짰던 코드를 뒤집어 까고, 막판에는 새벽 3시까지 개발을 하다 잤다(사담이지만 일주일 내내 에러나는 악몽에 시달렸다^^...) 

지금에서야 회고를 쓰면서 내가 만든걸 멀리서 보니 아쉬운점이 보이는 거지만, 정말 최선을 다했기에 여전히 내 사이트는 사랑스럽다. 12시가 넘으니 감성적이게 됐지만서두...

마지막으로 프리 때부터 약 한달 반을 함께한 팀원들에게, 질문을 받아주시고 항상 답변을 주신 멘토님께도 감사한 마음을 전한다.

 

 

 

 

내가 그린 선인장으로 마무리!

(tmi 캐릭터랑 똑같이 생겼다는말 3번 들음)

'Js&React 실습 > React Prac' 카테고리의 다른 글

[React/pre-project] stackoverflow 클론 코딩  (0) 2022.09.10
[React] Colorful Diary  (0) 2022.09.03