반응형
원문 : https://tkdodo.eu/blog/react-query-data-transformations
어떤 방법이 가장 좋을까?
그때그때 다르다.
- 모든 개발자, 항상
0. GraphQL 사용과 백엔드 개발자의 지원
우리가 할 일이 없음. 하지만 항상 가능한 것이 아님.
1. 쿼리 함수 안에서(queryFn)
queryFn은 useQuery에 전달하는 함수입니다.
Promise를 반환할 것으로 예상하고 결과 데이터는 쿼리 캐시에 저장됩니다.
이 함수가 항상 백엔드가 제공하는 구조로 데이터를 반환할 필요는 없습니다.
const fetchTodos = async (): Promise<Todos> => {
const response = await axios.get('todos')
const data: Todos = response.data
return data.map((todo) => todo.name.toUpperCase())
}
export const useTodosQuery = () => useQuery(['todos'], fetchTodos)
프론트엔드에서 이 데이터를 "백엔드에서 이렇게 같이 가져온 것처럼" 작업할 수 있습니다.
즉, 원래 구조에 액세스할 수 없습니다. react-query-devtools를 보면 변형된 구조를 볼 수 있습니다.
네트워크 트레이스를 보면 원래 구조를 볼 수 있습니다. 헷갈릴 수 있으니 참고하세요.
🟢 코로케이션 측면에서 "백엔드와 매우 가까움"
🟡 변형된 구조가 캐시에 저장되므로 원래 구조에 액세스할 수 없습니다.
🔴 데이터를 가져올 때마다 실행
🔴 자유롭게 수정할 수 없는 공유 API 레이어가 있는 경우 불가능
2. 렌더 함수 안에서 (render function)
커스텀 훅을 사용하면 쉽게 변환을 수행할 수 있습니다.변환 연산이 항상 수행되므로, 연산을 메모할 수 있는데,
data는 참조적으로 안정적이지만, queryInfo는 아니므로 조심하세요
const fetchTodos = async (): Promise<Todos> => {
const response = await axios.get('todos')
return response.data
}
export const useTodosQuery = () => {
const queryInfo = useQuery(['todos'], fetchTodos)
return {
...queryInfo,
// 🚨 don't do this - the useMemo does nothing at all here!
data: React.useMemo(
() => queryInfo.data?.map((todo) => todo.name.toUpperCase()),
[queryInfo]
),
// ✅ correctly memoizes by queryInfo.data
data: React.useMemo(
() => queryInfo.data?.map((todo) => todo.name.toUpperCase()),
[queryInfo.data]
),
}
}
🟢 useMemo를 통해 최적화 가능
🟡 정확한 구조는 devtools에서 검사할 수 없습니다.
🔴 좀 더 복잡한 구문
🔴 데이터는 undefined일 수 있음
3. Select 옵션 사용
v3에는 데이터 변환에도 사용할 수 있는 빌트인 셀렉터가 도입되었습니다.
export const useTodosQuery = () =>
useQuery(['todos'], fetchTodos, {
select: (data) => data.map((todo) => todo.name.toUpperCase()),
})
셀렉터는 데이터가 존재하는 경우에만 호출되므로 여기에서 undefined에 대해 신경 쓸 필요가 없습니다.
변환 비용이 많이 든다면 useCallback을 사용하거나 안정적인 함수 참조로 추출하여 메모화할 수 있습니다.
const transformTodoNames = (data: Todos) =>
data.map((todo) => todo.name.toUpperCase())
export const useTodosQuery = () =>
useQuery(['todos'], fetchTodos, {
// ✅ uses a stable function reference
select: transformTodoNames,
})
export const useTodosQuery = () =>
useQuery(['todos'], fetchTodos, {
// ✅ memoizes with useCallback
select: React.useCallback(
(data: Todos) => data.map((todo) => todo.name.toUpperCase()),
[]
),
})
또한 select 옵션을 사용하여 데이터의 일부만 구독할 수도 있습니다.
HOC와 함께 useSelector 같은 API를 만들 수 있습니다.
export const useTodosQuery = (select) =>
useQuery(['todos'], fetchTodos, { select })
export const useTodosCount = () => useTodosQuery((data) => data.length)
export const useTodo = (id) =>
useTodosQuery((data) => data.find((todo) => todo.id === id))
🟢 최고의 최적화
🟢 부분 구독 허용
🟡 관찰자마다 구조가 다를 수 있음
🟡 구조적 공유(structural sharing)는 두 번 수행됩니다.
- https://itchallenger.tistory.com/577 참고
- queryFn에서 반환된 결과에 변경된 사항이 있는지 확인 후
- 셀렉터 함수의 결과에 대해 한 번 더 수행됩니다.
반응형
'FrontEnd' 카테고리의 다른 글
리액트 쿼리 : 쿼리 상태 체크 (0) | 2022.06.18 |
---|---|
리액트 쿼리 : 렌더링 최적화 (0) | 2022.06.17 |
리액트 쿼리 : 반드시 알아야 하는 상식 (0) | 2022.06.17 |
useState에 대해 좀 더 잘 알아보기 (0) | 2022.06.17 |
useState의 함정 : useEffect와 사용 시 주의할 점. (0) | 2022.06.16 |