Pick-Git의 프론트엔드 비동기 상태관리 전략
이 글은 저희의 프로젝트 ‘깃-들다’에 적용한 리액트 쿼리 전략에 대해서 다룹니다. 관련된 코드는 아래 링크에서 확인하실 수 있습니다.
https://github.com/woowacourse-teams/2021-pick-git/tree/develop/frontend
이 글을 읽는데 필요한 사전 지식은 아래 링크에서 찾아보실 수 있습니다.
https://2021-pick-git.github.io/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%EB%B0%8F-%EA%B4%80%EB%A0%A8-%ED%8C%81/
개요
저희 프로젝트의 프론트엔드 개발은 리액트를 기반으로 구현되었고 상태관리를 위해 리액트 쿼리 외에 다른 상태관리 라이브러리를 사용하지 않고 있습니다. 이 글에서는 저희가 왜 리액트 쿼리 라이브러리를 사용하기로 선택했는지, 그리고 정확히 어느 부분에서 어떤 전략에 따라 비동기 상태관리를 수행했는지를 다뤄보고자 합니다.
왜 비동기 상태 관리 라이브러리를 적용했는가?
저희는 많은 라이브러리를 사용하는 것이 개발 편의성을 증진시키더라도 번들 사이즈를 증가시킬 수 있다는 것을 알고 있습니다. 그러나 저희 프로젝트의 기능 구현 완성 기한은 6주 정도로 매우 제한적인 상황이었고, 저희가 개발하고자 하는 서비스는 개발자들을 위한 SNS 성격을 지니고 있었기 때문에 정말 많은 비동기 로직을 작성해야 할 것이 예상되었습니다. 따라서 번들 사이즈의 희생을 감수하더라도 개발 생산성을 크게 높일 필요가 있다고 판단기에 비동기 상태 관리 만큼은 라이브러리를 적용하기로 결정했습니다.
왜 비동기 상태 관리 라이브러리 중 React-Query 를 선택했는가?
저희가 알고 있는 신뢰할 수 있는 비동기 상태 관리 라이브러리는 React Query, SWR, Apollo Client, RTK-Query 가 있었습니다. 이 중 Apollo Client 는 저희 프로젝트에서 Apollo 를 채택하지 않았기 때문에 자동으로 선택지에서 제외했고, RTK-Query 는 사용하기 위해서는 Redux 의 사용이 강제되었기 때문에 이 또한 제외시켰습니다. 저희는 결국 React Query 와 SWR 중 하나를 선택해야 했습니다.
번들 사이즈 비교
react-query 는 다른 상태관리 라이브러리에 비해 많은 기능과 편의성을 자랑하지만, 그만큼 큰 번들 사이즈를 가지고 있습니다. 이에 비해 SWR 은 리액트 쿼리의 1 / 3 보다 작은 번들 사이즈를 지니고 있었습니다.
기능적 효용 비교
저희 서비스는 기본적으로 SNS 이기 때문에 게시글을 작성하는 것 외에도 좋아요, 댓글 달기 관련한 수정 및 삭제 로직이 많습니다. 이것은 단순히 비동기 요청을 통해 데이터를 가져오고 이를 캐싱하는 것 외에도 해당 비동기 상태의 변경 로직이 많다는 것을 의미했습니다. SWR 은 React Query 와 비교해서 Mutation 부분에서의 편의 기능 (isFetching, Error 등의 변수 제공 X)이 부족했고 저희는 React Query를 사용하는 것이 SWR 을 사용하는 것보다 더 생산적으로 코드를 작성하는데 적합하다고 판단했습니다.
또한 SWR 은 CacheTime 기능을 제공하지 않았습니다. 이는 서비스를 이용하면서 쌓여가는 캐싱된 데이터들에 대한 가비지 컬렉션을 지정된 시간에 맞게 수동으로 수행되도록 만드는 코드를 작성해야 한다는 것을 의미했고, 이는 생산성을 저하시킬 것이라 생각했습니다. 또한 React Query 의 cacheTime 기능을 적절히 이용하면 너무 오래된 데이터는 화면 상에 보이지 않고 다시 로딩 컴포넌트가 보이는 식의 렌더링 컨트롤 또한 세밀하게 수행할 수 있을 것이라 생각했습니다.
결론
번들 사이즈를 생각하면 SWR 이 현명한 선택인 듯하지만, 저희는 원래 목표했던 생산성을 더욱 극대화시킬 수 있는 React Query 를 선택하기로 결정했습니다.
데이터 조회 전략
prefetching
이용자의 profile 정보와 같은 데이터는 요청의 크기가 작고, 서비스를 이용하면서 이용자가 사용할 가능성이 높은 데이터이기 때문에 profile 페이지에 접속하지 않더라도 미리 가져오도록 설정하였습니다.
infinite scrolling
저희 서비스는 SNS 의 특성 상 가져와야 할 데이터의 양이 많기 때문에 한번에 모든 데이터를 불러오지 않을 수 있도록 절반이 넘는 비동기 상태를 infinite scrolling 방식으로 가져오고 있습니다. 이를 위해 react query 에서 제공하는 useInfiniteQuery 훅을 사용하여 새로 불러온 데이터 외에 기존 데이터는 업데이트 하지 않고 있습니다.
cache time
최신 상태 반영 중요도가 높지 않다고 생각되는 게시글 데이터에 대해서는 cache time 을 4시간으로 설정하여 서비스를 이용하는 대부분의 시간 동안 캐싱된 데이터가 삭제되지 않도록 설정하였습니다.
하지만 다른 이용자의 포트폴리오 관련 데이터는 최신 상태 반영에 대한 중요도가 높다고 판단했습니다. 이에 cache time 을 0으로 설정하여 매번 페이지에 접근할 때마다 이전에 불러온 데이터가 잠깐 보였다가 업데이트 되는 것이 아닌, 매번 로딩 후 페이지가 렌더링 되도록 설정하였습니다.
데이터 변경 전략
수정, 삭제, 추가된 데이터를 화면 상에 즉각 반영
수정, 삭제, 추가 작업에 대한 요청이 실패할 확률이 적기 때문에 설령 서버에서 응답이 도착하지 않았더라도 화면 상에 데이터 변경에 따른 렌더링을 수행합니다. 그 후 서버에서 보내준 데이터를 바탕으로 렌더링을 다시 한번 수행하여 서버 상태와의 싱크를 맞춥니다.
에러 발생 시 이전 상태로 롤백
혹시라도 데이터 수정, 삭제, 추가 요청이 실패했을 경우엔 데이터가 변경되기 이전 상태를 반영하도록 렌더링을 다시 수행합니다. 이때 요청이 실패함과 동시에 화면 상에 변경 사항 또한 취소되었음을 이용자에게 snackbar 컴포넌트를 통해 피드백합니다.