[Step2.2] cactus: Zustand 적용하기 - #6
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
There was a problem hiding this comment.
[논의]
스토어 구조 변경 시 수정 지점을 훅 하나로 모으려는 의도는 이해됩니다. 다만 현재 이 훅을 소비하는 컴포넌트가 2개이고, 두 컴포넌트가 사용하는 선택자도 서로 다릅니다. 컴포넌트 수가 많거나 동일한 선택자 묶음을 반복하는 상황이라면 이 패턴이 유효하지만, 현재 규모에서는 추상화 레이어 추가로 인한 코드 추적 비용이 더 크다고 생각합니다.
There was a problem hiding this comment.
그렇네요 각 컴포넌트가 실제로 사용하는 선택자가 달라서 지금은 반복을 줄이는 역할보다는 모아두는 역할에 가깝다는 생각이 듭니다. 스토어 구조가 바꿀 때 한 곳만 수정하면 된다는 의도였는데, 현재 규모에서는 유성님 말씀처럼 추상화 비용이 더 클 수 있을 것 같아요. 컴포넌트 별로 필요한 선택자만 직접 구독하는 방식으로 다시 반영해보겠습니다.
| }), | ||
| { | ||
| name: "self-paced-react-category", | ||
| storage: createJSONStorage(() => sessionStorage), |
There was a problem hiding this comment.
[배움]
저는 localStorage를 사용했는데 카테고리 필터의 경우에는 지금 내가 보고 있는 화면의 상태로 다음에 앱을 열면 전체 목록부터 다시 보는 것이 자연스러운 탐색 상태라고 생각되네요! 이 점에서 sessionStorage가 더 적절한 것 같습니다.
| const { fetchRestaurants } = useRestaurantData(); | ||
| useEffect(() => { | ||
| fetchRestaurants(); | ||
| }, [fetchRestaurants]); | ||
|
|
There was a problem hiding this comment.
[제안]
fetchRestaurants 호출을 App에서 하고 있는데, 이 경우 RestaurantList는 누군가 먼저 페칭을 해줬을 것이라고 암묵적으로 가정하게 됩니다. RestaurantList 코드만 봤을 때 데이터가 어디서 채워지는지 알 수 없어서, 나중에 App에서 호출이 빠지거나 컴포넌트를 다른 곳에서 재사용할 때 빈 목록이 나오는 원인을 찾기 어려울 수 있다고 생각합니다.
데이터가 필요한 RestaurantList가 직접 fetchRestaurants를 호출하면, 이 컴포넌트가 어떤 데이터를 언제 가져오는지 코드에서 바로 파악할 수 있어서 더 적절하다고 생각해요!
There was a problem hiding this comment.
말씀해주신 것처럼 co-location 원칙 기준으로는 RestaurantList가 자신의 데이터 의존성을 직접 선언하는 게 더 명시적이라는 점은 동의합니다. 다만 저는 두 가지 이유로 App에서 호출하는 방식을 선택했습니다!
우선 co-location의 장점이 가장 잘 드러나는 건 컴포넌트를 다른 곳에서 재사용할 수 있을 때인데, RestaurantList는 이미 useRestaurantData()에 결합된 도메인 컴포넌트라 독립적으로 이동하는 상황이 없다고 생각합니다. 두 번째로 React Query는 중복 요청을 자동으로 처리해주지만, 순수 Zustand에서는 중복 fetch를 직접 관리해야 합니다. RestaurantList에 fetch를 두면 guard 로직을 별도로 추가해야 하는데, App에서 한 번만 호출하면 그 문제를 구조적으로 피할 수 있다고 생각했습니다.
개인 목표 달성 여부
create,set,get, selector)을 직접 마이그레이션하며 손에 익힌다.리뷰어에게
useRestaurantData커스텀 훅으로 감싼 구조가 이 규모에서 적절한지, 오히려 과한 추상화인지 의견이 궁금합니다.Zustand를 사용한 이유와 trade-off
왜 Zustand로 마이그레이션했는가
Context API는 value 전체가 바뀌면 구독하는 모든 컴포넌트가 리렌더링됩니다. 상태를 잘게 쪼개거나
useMemo로 감싸지 않으면 불필요한 리렌더링이 생기는 구조입니다.Zustand는 selector로 구독한 값만 바뀔 때 리렌더링되고, Provider 없이 어디서든 import해서 사용할 수 있습니다. Context의 보일러플레이트(
createContext+ Provider +useContext+ 커스텀 훅)도create하나로 대체됩니다.trade-off
create하나Store 분리 기준
useRestaurantStore— 서버 데이터(newRestaurants,isLoading,error,fetchRestaurants,registerRestaurant)useFilterStore— 카테고리 필터 상태 +persist미들웨어 (sessionStorage)모달 열림/닫힘은 App 직계 자식에만 영향을 주는 일시적인 UI 상태라 App 로컬 state로 유지했습니다.
카테고리 필터 persist
persist미들웨어로 카테고리 선택값이 새로고침 후에도 유지되도록 했습니다. localStorage 대신 sessionStorage를 사용했는데, 카테고리 필터는 브라우저를 닫아도 유지해야 할 설정값이 아니라 현재 탐색 중인 임시 상태에 가깝기 때문입니다.props를 유지한 부분
RestaurantListselectedCategory,onRestaurantClickselectedCategory는 store에서 직접 꺼낼 수도 있지만, "받은 카테고리로 필터링"하는 역할을 명확히 하기 위해 props 유지.onRestaurantClick은 App 로컬 상태를 변경하는 핸들러라 props가 적합.CategoryFiltercategory,onCategoryChangeModaltitle,onClose,children