리액트 쿼리 셀렉터 실무 사용 경험, 폴더 구조에 대한 생각을 공유합니다.
리액트 쿼리 셀렉터
놀랍게도 리덕스처럼 리액트 쿼리에는 셀렉터가 있습니다.
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
좀더 실무적으로 묘사하면 아래와 같이 되겠네요.
- 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
대부분의 앱에서 서버 응답을 그대로 사용하는 경우는 없습니다.
위 글에 따르면 서버 응답을 변환하기에 최적의 위치는 selector입니다.
그런데 todo와 같이 아이템의 값이 바뀌고, 순서가 바뀌는 경우는, 정규화해주면 상태 관리와 최적화에 참 좋습니다.
데이터 모양은 아래 글을 참조합니다.
https://itchallenger.tistory.com/677
이 경우에는 fetch에서 정규화 하고, queryCache를 직접 조작하는 낙관적 업데이트 방식을 사용하면 좋습니다.
수정본과 원본을 동시에 유지해야 한다면, 퍼지 쿼리키 매칭을 사용하면 됩니다.
example queryKey : [{scope:'todo', draft : true}]
자세한 내용은 아래 글을 참조합니다.
https://itchallenger.tistory.com/583
이때도 컨테이너 컴포넌트에 리액트 쿼리 specific한
queryClient.setQueryData(['todos', newTodo.id], newTodo) 같은 API가 노출되지 않도록 주의합시다.
external 라이브러리 결합도는 최대한 피해야 하는 결합도 입니다.
queries.ts 파일이 퍼사드(혹은 레포지토리) 역할을 하도록 합시다.
'FrontEnd' 카테고리의 다른 글
[번역] Mobx로 배우는 반응형 프로그래밍(reactive programming)의 원리 (0) | 2022.09.12 |
---|---|
[30초 CSS] CSS 속성 상속과 !important는 무관하다 (0) | 2022.09.02 |
[번역] Idiomatic Redux: The Tao of Redux, Part 2 - Practice and Philosophy (0) | 2022.08.28 |
[Redux] 액션 생성자를 사용해야 하는 이유 (Idiomatic Redux: Why use action creators?) (0) | 2022.08.27 |
[번역] Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent (0) | 2022.08.27 |