- 프로젝트: loplat X mobile
- 키워드: recoil, 렌더링 최적화
- 상황
- loplat X mobile 은 CRA 에 기반하여 CSR 방식을 사용한다.
- 로그인이 필요한 '캠페인 목록 페이지'에서 로그아웃 버튼을 누르면, protected router HOC에 의해 자동으로 로그인 페이지로 이동한다.
- 로그아웃을 시키는 것 외에 side effects 가 발생하면 안되는 상황이지만, 로그인 페이지로 이동하기 전 캠페인 목록 페이지에서 '캠페인 목록 GET API'가 의도치않게 한 번 호출되는 버그가 있다며 팀원분이 도움을 요청하셨다.
- 해결과정
- 먼저 API를 어떤 코드에서 호출하는지 살펴보았다.
위와 같이 react-query 라이브러리의 useInfiniteQuery를 사용하여 API를 호출하고 있었다.const { ... data: ExampleData, ... } = useInfiniteQuery( ['campaigns', { status }], async ({ pageParam = 1 }) => { ... const { data } = await ExampleApi.fetchCampaigns(newParam); return data; }, { ...options, refetchOnMount: 'always', refetchOnWindowFocus: false, } );
하지만 API가 호출되는 경우는 페이지가 mount 될 때와 pageParam이 업데이트 되는 경우 뿐이었다.
로그인이 풀리고 로그인 페이지로 이동하는 로직에서 API가 다시 호출될 이유는 없어보였다. - 그럼에도 불구하고 API가 호출된다는 것은 캠페인 목록 페이지가 의도치 않게 리렌더링(remount)된다는 증거였다. 따라서 React.memo를 사용하여 부모 컴포넌트가 리렌더링되더라도, 캠페인 목록은 리렌더링되지 않도록 감쌌다.
https://ko.reactjs.org/docs/react-api.html#reactmemo
위와 같이 적용하니 API가 호출되는 버그는 사라졌다.export default React.memo(CampaignsPage);
하지만 공식문서에 나와있듯이, React.memo는 최적화를 위한 수단이므로 리렌더링을 막기위해서만 쓰는 것은 잘못된 사용이었다. - 근본적인 원인을 찾기위해 페이지들의 부모 컴포넌트인 Router를 살펴보았다.
Router의 로직과, 인증관련HOC에서는 리렌더링을 유발할만한 로직(상태 변경 등)을 찾을 수 없었다.<Switch> {routes.map((route) => ( <Route key={route.path} path={route.path} component={인증관련HOC(route.component)} exact={route.exact} /> ))} </Switch>
- Router의 부모 컴포넌트인 App.tsx에 와서 코드를 더 살펴보았다.
위와 같은 코드를 발견했지만 처음 보기엔 별 문제가 없어보였다. 하지만 버그를 유발할만한 코드가 여기말고는 없었기 때문에, 이 코드가 왜 잘못된 코드인지 생각해보았다.const [, setAuth] = useRecoilState(userInfo);
로그아웃을 할 때 useInfo의 상태가 변하면 [_, setAuth] 에서 상태(_)를 명시하지 않아도 App이 리렌더링 되는걸까. 그렇다면 자식인 '캠페인 목록 페이지'도 리렌더링 될 것이다.
recoil을 직접/많이 사용해본 경험은 없었지만, 스치듯 recoil setter 함수에 대한 문서를 본 기억이 나서 관련된 내용을 검색해보았다. - https://recoiljs.org/ko/docs/api-reference/core/useSetRecoilState/
"만약 컴포넌트가 setter를 가져오기 위해 useRecoilState() hook을 사용한다면 업데이트를 구독하고 atom 혹은 selector가 업데이트되면 리렌더링을 합니다."
공식 문서를 통해 useRecoilState만 쓸 경우 불필요한 리렌더링이 일어날 수 있다는 것을 알게 되었다.
위와 같이 useSetRecoilState를 이용하여 setter함수만 리턴받으니 버그가 해결되었다!const setAuth = useSetRecoilState(userInfo);
- 먼저 API를 어떤 코드에서 호출하는지 살펴보았다.
- 의의
- hook 사용시 의도치않은 리렌더링이 일어나지 않도록 주의하자
- 공식문서를 꼼꼼히 읽어두자
'프론트엔드 > React' 카테고리의 다른 글
선언형 Portal, Modal 컴포넌트 구현하기 (0) | 2022.02.15 |
---|---|
Toast 컴포넌트 구현하기 (1) | 2022.01.26 |
리액트에서 달팽이 모양으로 움직이는 애니메이션 만들기 (0) | 2022.01.19 |
SVGR id collision issue 해결하기 (0) | 2021.11.30 |
Firebase auth 정보를 redux로 관리하기 (0) | 2021.11.18 |