이전 섹션에서 React Query의 데이터 변환 에서
데이터의 길이가 변경될 경우에만 다시 렌더링된다고 정의했지만, 기술적으로 사실이 아니다. 🤦
background-fetch
를 다시 실행할 때마다 구성 요소는 다음 쿼리 정보를 두번 re-render 처리합니다.
{ status: 'success', data: 2, isFetching: true, isLoading: true, ... }
{ status: 'success', data: 2, isFetching: false, isLoading: false, ... }
isFetching
은 어떠한 react query 요청 내부의 비동기 함수 처리여부에 따라 true/false
상태로 처리됩니다.
➡ 캐시 유무와 상관없이 데이터를 가져오는 유무만 확인하여 true/false
상태로 처리합니다.
isLoading
은 캐시 데이터조차 없이, 처음 실행된 쿼리일 때 loading
에 따라 true/false
상태로 처리됩니다.
➡ 어떤 데이터를 호출하여 웹 브라우저 상에 캐시가 존재한 상태에서 같은 쿼리 키의 데이터
를 중복으로 호출한다면 isLoading
은 무조건 false
상태입니다.
isFetching
와isLoading
은 비슷하게 loading 개념을 사용하지만 기존 캐시 데이터 여부에 따라 다르게 동작합니다.
isFetching
: 서버에 데이터 요청을 다시 할 경우 (캐시 데이터가 존재할 때)isLoading
: 서버에 데이터 요청을 처음 할 경우react query는 notifyOnChangeProps
옵션이 있습니다.
props 중 하나가 변경된 경우에만 해당 observer
에게 변경 사항을 알려주도록 observer
수준에서 설정할 수 있습니다.
export const useTodosQuery = (select, notifyOnChangeProps) =>
useQuery(['todos'], fetchTodos, { select, notifyOnChangeProps })
export const useTodosCount = () =>
useTodosQuery((data) => data.length, ['data'])
이렇게 적용하면 useQuery의 response 값의 data
만 변경 된 경우 리렌더링이 됩니다.
export const useTodosCount = () =>
useTodosQuery((data) => data.length, ['data'])
function TodosCount() {
// 🚨 we are using error, but we are not getting notified if error changes!
const { error, data } = useTodosCount()
return (
<div>
{error ? error : null}
{data ? data : null}
</div>
)
}
🚨 status를 이용하여 조건부 렌더링을 하는 경우 문제가 발생함
➡ status 의 변경여부를 감지할 수 없음
notifyOnChangeProps
목록을 컴포넌트에서 실제로 사용하고 있는 필드와 동기화하면 됩니다.🚨 Custom Hook에서 하드코딩한 경우 실제로 무엇을 사용할지 모르기 때문에 문제가 발생
➡ 동기화 작업을 하지 않고 데이터 속성만 관찰하는 경우 오류가 표시되면, 컴포넌트가 다시 렌더링되지 않으므로 오래된 값 입니다.
💡 notifyOnChangeProps
를 tracked
로 설정하면 react query는 렌더링 중에 사용중인 필드를 추적하며, 이 필드를 사용하여 목록을 계산합니다.
➡ 목록을 수동으로 지정하는 것과 정확히 동일한 방식으로 최적화되며, 사용자가 고려할 필요가 없습니다.
v4 부터는 기본적으로 notifyOnChangeProps: 'tracked' 옵션이 설정되어 있습니다.
➡ notifyOnChangeProps: 'all' 설정하여 옵트아웃 할 수 있습니다.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
notifyOnChangeProps: 'tracked',
},
},
})
일반적인 분해 할당은 괜찮지만, 그 외는 하지마세요.
// 🚨 will track all fields
const { isLoading, ...queryInfo } = useQuery(...)
// ✅ this is totally fine
const { isLoading, data } = useQuery(...)
render
중에만 작동합니다.작동 중인 필드에만 엑세스하면 추적되지 않습니다.
🚨 종속성 배열로 인해 매우 어려운 경우
const queryInfo = useQuery(...)
// 🚨 will not corectly track data
React.useEffect(() => {
console.log(queryInfo.data)
})
// ✅ fine because the dependency array is accessed during render
React.useEffect(() => {
console.log(queryInfo.data)
}, [queryInfo.data])
추적된 쿼리는 각 render에서 재설정되지 않으므로 필드를 한 번 추적하면 관찰자(Observer)의 수명동안 추적됩니다.
const queryInfo = useQuery(...)
if (someCondition()) {
// 🟡 we will track the data field if someCondition was true in any previous render cycle
return <div>{queryInfo.data}</div>
}
이 기능을 사용한다면 모든 수준에서 데이터 참조 ID를 유지할 수 있습니다.
[
{ "id": 1, "name": "Learn React", "status": "active" }<,
{ "id": 2, "name": "Learn React Query", "status": "todo" }
]
다음과 같은 데이터 구조에서 첫 번째 todo를 'done' 상태로 전환하고 re-fetch를 한다면, backend에서 새로운 jsonData 값을 얻을 수 있습니다.
[
- { "id": 1, "name": "Learn React", "status": "active" },
+ { "id": 1, "name": "Learn React", "status": "done" },
{ "id": 2, "name": "Learn React Query", "status": "todo" }
]
react query는 이전 상태와 새 상태를 비교하며, 가능한 많은 이전 상태를 유지합니다.
todo를 업데이트했기 때문에 todos 배열 데이터가 새로워집니다.
➡ id:2
에 대한 개체는 이전 상태의 개체와 동일한 참조가 됩니다.
참조