우선 전반적으로 프로그램이 무겁지 않아서 그런지 낭비를 줄일만한 곳을 크게 발견하지 못했다.
최적화 작업을 통해 좀 더 다이나믹한 before/ after를 기대했지만 규모가 큰 프로젝트에서 꼭 다시 진행해봐야겠다.
React.lazy와 loadable-components 라이브러리를 사용할 수 있다.
둘다 파일을 미리 요청해서 갖고있지 않고 필요한 시점이 되어 파일을 요청하는건데
React.lazy는 아직 서버 사이드 렌더링을 할 수 없어 서버사이드렌더링이 필요하다면 Loadable Components를 추천한다고 한다.(리액트피셜)
이번 프로젝트에서는 lazy를 사용했다.
기존에 import MainPage from('/page/MainPage')
를 컴포넌트 내에서 const MainPage = React.lazy(()=>import('/page/MainPage')
와 같이 작성하면된다.
실제 사용 전후를 비교해보았다.
이슈트래커 프로젝트에서는 엔트리 페이지에서 로그인 여부를 확인하고
로그인이 되어있다면 메인페이지로 리다이렉팅, 아니면 로그인 페이지로 안내하는데
엔트리 페이지여서 메인과 로그인 페이지 외에 여러 페이지들이 라우팅 되어있는 곳이었다.
여기서 적용하고나니 엔트리 페이지 로딩속도 2.8s => 1.34s 단축
작은 차이지만 작지 않은 차이 같다
before)
after)
결과로 IssueDetailOption 컴포넌트에 Memo가 적용된 것을 볼 수 있다.
이는 CreateIssuePage에서 이슈를 작성하는 행동을 했을때 렌더링될 필요가 없는 option탭이 렌더링되던 상황을 수정한 것이다.
before)
after)
처음엔 개발자도구를 켜놓고 파란불이 들어오는(렌더링이 되는 부분) 부분들을 모두 React.memo로 감쌌는데 이 함수를 적용시키는데도 처리가 필요한 것이라 정말 필요한 부분에 적용되어 한다. 이 React.memo는 prop이 자주 변경되는 경우는 memo와 상관없이 새로 렌더링이 되기 때문에 prop의 변형이 거의 없는 컴포넌트에 적용해야 효과를 볼 수 있다.
예시로 input컴포넌트에 memo를 걸었었는데, input에 입력을 할 때마다 새로 렌더링이 되기 때문에 이 곳에서의 React.memo는 불필요하다. 위의 예처럼 변경이 없는 고정적인 컴포넌트(옵션탭이었다.)가 여러번 렌더링 되는것을 막는데 memo가 효과적으로 쓰인다.
이번 프로젝트에서도 Recoil로 상태관리를 했다. 저번 에어비앤비 미션때는 주로 GET요청을 하였기 때문에 리코일이 캐싱을 하는 특징이 렌더링을 더 빨리해주어 되게 좋은 점이라고 생각했었는데, 이번 프로젝트에서는 POST요청이 많아 애를 먹었다.
POST요청으로 상태값을 변경시켜도 해당 데이터를 렌더링하는 곳에서는 상태값이 변경된 것을 알지 못하기 때문에 POST요청을 하는 곳(ex-이슈생성)과 렌더링할 데이터를 받아오는 GET요청(ex-이슈목록) 두 군데에서 하나의 동일한 상태값을 공유할 필요가 있었다.
각 리코일 셀렉터마다 trigger를 설정해두었고 POST요청 시 trigger를 변경하여 GET 리코일셀렉터 내에 정의해둔 trigger가 바뀐 상태값을 인지하고 자동으로 렌더링하도록 했다.
여기서도 작은 문제가 있었는데,
before) trigger를 true/ false로 번갈아가며 요청을 하니까 새로 바뀐 값을 보여주겠지?
setTrigger(trigger⇒!trigger)
await fetch(url)
결과는 NO 였고, (하도 어이없어서 영상까지 찍음, 분명 삭제했는데... 니가 왜 거기서 나와🤯)
이유는 true상태와 false상태, url로만 같은 요청인지를 판단했기 때문에
첫번째 요청은 새로운 렌더링을 보여주었지만 두번째부터는 이전값을 불러오는 이해할 수 없는 상황이 발생되었었다. (지금은 이해하지만...)
신기한 건 POST요청 시 payload로 전달하는 인자값이 달라지니 당연히 다른 요청으로 인지할 것이라는 생각이 틀렸다는 것이었다.
리코일은 정말 리코일 셀렉터 내의 get과, url을 기반으로만 요청을 구분하였다.
이걸 해결하는 방법은 recoiliFamily로 인자를 받아 사용하는 것이다. selector와 다르게 recoilFamily는 인자값의 변경을 인지하여 캐싱하지 않는다.
하지만, 프로젝트 구조상 useEffect나 이벤트 콜백함수 내에 리코일을 정의할 수 없어 recoilFamily는 적용이 어렵지 않았을까 싶다. 이것에 대한 해답은 좀 더 알아봐야할 것 같다.
after)
무튼 이 문제를 해결하기 위한 방법은 trigger를 매번 다르게 해주어야 하는 것이었고
아래와 같이 하면 해결이 되었다.
setTrigger(trigger⇒trigger+1)
await fetch(url)
이번엔 정말 잘해보려고했는데 아직도 아쉬움이 남는다.
Suspense를 적용해 로딩되는 사이에는 progressBar를 보여주게했다.
근데 생각보다 로딩이 오래걸리는 지점이 초반에 페이지 라우팅할 때를 제외하고는 별로 없어 눈에 띄질 않았다.
에러처리는 로그인 토큰 유무를 판단하며 해야했는데, 로그인 토큰이 만료되면 에러를 반환하게 되어있고 해당 에러를 접하면 다시 로그인 페이지로 이동하게했고 그 외의 잘못된 주소로의 접근에 대해서는 다시 메인페이지로 돌아가게 리다이렉트시켜 사용자 입장에서 에러에 대한 처리를 최대한 부드럽게 했다. 다만 매 요청마다 중복되는 코드 작성이 불가피했다. (로그인토큰 만료 시, 로그아웃처리 후 리다이렉트 시킬 때, history와 같은 API를 커스텀 훅안에서 사용할 수 없었기 때문이다.)
다음 프로젝트에는 좀 더 체계적인 에러처리를 하고 싶고, 이 과정에서 백엔드와 면밀히 소통해야겠다고 느꼈다. 로그인이 무효화되었을 시의 정확한 에러코드나 에러반환값 설정을 하여 좀 더 수월하게 작업을 해야겠다.
코드스쿼드에서 마지막 미션이 종료되었다.
이미 종료된지는 오래였지만 리팩토링해보고 기능을 좀더 추가해보며 개발기간을 더 길게 가졌다.
가장 많은 시간을 들인 만큼 고민거리도 많았고,
팀원들과 일하는 방식에서 어떻게 소통해야하는지 배우고 느낀게 많았다.
또 마지막으로 팀원들과 함께 회고하는 자리를 가지면서 깔끔히 마무리를 한 것 같아 좋았고
팀원들에게 한마디 씩 남겼지만 , 좋으신분들과 기분좋게 소통하고 많이 배울 수 있는 기회였다.
이제 다시 코드스쿼드에서 미션할때처럼 열심히...! 새로운 프로젝트에 열의를 쏟아야지
아쉬운 점은 새 프로젝트 '하이파이'에서 풀어낼 수 있도록 !
정리가 아주아주아주 알찬데요??? 너무 좋네요!!!!