본문 바로가기

FrontEnd

리액트 쿼리 : 셀렉터와 폴더 구조

반응형

리액트 쿼리 셀렉터 실무 사용 경험, 폴더 구조에 대한 생각을 공유합니다.


리액트 쿼리 셀렉터

놀랍게도 리덕스처럼 리액트 쿼리에는 셀렉터가 있습니다.

export const useTodosQuery = (select, notifyOnChangeProps) =>
  useQuery(['todos'], fetchTodos, { select, notifyOnChangeProps })

위와 같이 셀렉터를 파라미터로 받아 사용할 수 있도록 하는게 좋을까요?

결론은 아닙니다. 리액트 쿼리의 api 디자인은 실제로 리덕스의 영향을 꽤 많이 받았으며,

리덕스의 핵심 철학 중 하나는 함수형 프로그래밍 원리를 따르는 것입니다. 이를 고려하면 데이터의 모양을 캡슐화해야 합니다.

셀렉터 함수의 중복, 메모화, 데이터 캡슐화를 고려하면, useData 형태로 export 하는 것이 좋습니다.

특히 타입스크립트를 사용한다면, select를 타이핑하기 생각보다 어려울 것입니다.

function fetchGroups(): Promise<Group[]> {
  return axios.get('groups').then((response) => response.data)
}

// ✅ data will be `number | undefined` here
function useGroupCount() {
  return useQuery('groups', fetchGroups, {
    select: (groups) => groups.length,
  })
}

리액트 쿼리 폴더 구조

리액트 쿼리 비공식 커뮤니티 문서에서는

리덕스와 같이 feature(domain or component or page or useCase....) driven 폴더 구조를 추천합니다.

해당 내용은 아래 번역 문서에서 볼 수 있습니다.

https://itchallenger.tistory.com/582

 

리액트 쿼리 : 쿼리키

원문 : https://tkdodo.eu/blog/effective-react-query-keys 쿼리 키 라이브러리가 내부적으로 데이터를 올바르게 캐시하기 위해 필요합니다. 쿼리 종속성이 변경될 때 자동으로 다시 가져올 수 있도록 하기 위

itchallenger.tistory.com

좀더 실무적으로 묘사하면 아래와 같이 되겠네요.

- src
  - features
    - Profile
      - index.tsx
      - queries.ts // codes relate to react query
      - profileSlice.ts // client global state with redux/zustand or etc
      - Profile.ts // container component
    - Todos
      - index.tsx
      - queries.ts // codes relate to react query
      - TodoSlice.ts // client global state with redux/zustand or etc
      - Todos.ts // container component

feature 폴더 아래의 queries는 해당 기능 / 유즈케이스 / 도메인을 완성하기 위한 쿼리, 뮤테이션 함수가 들어갑니다. (즉 리액트 쿼리 관련 한 모든 내용을 한곳에 둡니다.)

뮤테이션 혹은 쿼리는 리덕스의 액션 생성자 + 디스패치를 결합한 API라 생각할 수 있습니다.

따라서 쿼리키와 뮤테이션 키를 숨기는(액션 타입) 것은 액션 생성자를 사용하는 것과 동등합니다.

추가로 리덕스의 하나의 액션에 반응하는 여러개의 리듀서 개념을 차용하면,

(리덕스의 경우 여러 slice를 합쳐 하나의 다른 slice를 만드는 패턴을 추천합니다.)

다른 feature의 queries 폴더에서 다른 queries 폴더의 쿼리, 뮤테이션을 임포트해 활용하는 것은 전혀 문제가 되지 않습니다.

(물론 의존성 방향을 주의하여, 새 파일로 뽑아내는것도 고려합니다.)

 

 

TIP 1 :  비동기(혹은 부수효과)와 동기 로직을 오케스트레이션 할 일이 있으면 redux-toolkit 같은 친구를 사용하는게 아니라면

따로 helper.ts와 같은 파일을 뽑아내는 것도 좋습니다.

queries 파일 내의 함수들은 순수하진 않지만 리듀서랑 비슷한 역할을 합니다.

helper.ts내에서 훅을 정의해 레포지토리 레이어와 비슷하게 사용할 수 있습니다. (순수함수와 부수효과를 잘 분리한다면)

view 레이어는 도메인 객체와 비슷하게 취급할 수 있습니다.

 

TIP 2 : 컨테이너 컴포넌트에는 렌더링 관련 의존성 주입(클래스명, 데이터, 함수, 훅, 스타일시트 등), 데이터 반복 로직을 넣습니다.


데이터 정규화

https://itchallenger.tistory.com/576 

 

리액트 쿼리 : 서버 응답 변환 위치

원문 : https://tkdodo.eu/blog/react-query-data-transformations 어떤 방법이 가장 좋을까? 그때그때 다르다. - 모든 개발자, 항상 0. GraphQL 사용과 백엔드 개발자의 지원 우리가 할 일이 없음. 하지만 항상..

itchallenger.tistory.com

대부분의 앱에서 서버 응답을 그대로 사용하는 경우는 없습니다.

위 글에 따르면 서버 응답을 변환하기에 최적의 위치는 selector입니다.

그런데 todo와 같이 아이템의 값이 바뀌고, 순서가 바뀌는 경우는, 정규화해주면 상태 관리와 최적화에 참 좋습니다.

데이터 모양은 아래 글을 참조합니다.

https://itchallenger.tistory.com/677

 

상태 정규화하기[Redux][Normalizing State Shape][프론트엔드 상태관리]

https://redux.js.org/usage/structuring-reducers/normalizing-state-shape Normalizing State Shape: Why and how to store data items for lookup based on ID" data-og-host="redux.js.org" data-og-source-ur..

itchallenger.tistory.com

이 경우에는 fetch에서 정규화 하고, queryCache를 직접 조작하는 낙관적 업데이트 방식을 사용하면 좋습니다.

https://tanstack.com/query/v4/docs/guides/optimistic-updates?from=reactQueryV3&original=https://react-query-v3.tanstack.com/guides/optimistic-updates 

 

Optimistic Updates | TanStack Query Docs

When you optimistically update your state before performing a mutation, there is a chance that the mutation will fail. In most of these failure cases, you can just trigger a refetch for your optimistic queries to revert them to their true server state. In

tanstack.com

수정본과 원본을 동시에 유지해야 한다면, 퍼지 쿼리키 매칭을 사용하면 됩니다.

example queryKey : [{scope:'todo', draft : true}]

자세한 내용은 아래 글을 참조합니다.

https://itchallenger.tistory.com/583

 

리액트 쿼리 : Query Function Context로 queryFn 타이핑

원문 : https://tkdodo.eu/blog/leveraging-the-query-function-context 요점 인라인 함수를 사용하지 마세요. Query Function Context를 활용하세요. 객체 키를 생성하는 쿼리 키 팩토리를 사용하세요. 들어가며..

itchallenger.tistory.com

이때도 컨테이너 컴포넌트에 리액트 쿼리 specific한

queryClient.setQueryData(['todos', newTodo.id], newTodo) 같은 API가 노출되지 않도록 주의합시다.

external 라이브러리 결합도는 최대한 피해야 하는 결합도 입니다.

queries.ts 파일이 퍼사드(혹은 레포지토리) 역할을 하도록 합시다.

 

 

반응형