
참조한 공식 문서
https://tanstack.com/query/latest/docs/framework/react/guides/queries
A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server. If your method modifies data on the server, we recommend using Mutations instead.
쿼리는 고유 키에 연결된 비동기 소스 데이터에 대한 선언적 종속성이라고 한다.
서버로부터 데이터를 받아오는 연결이 우리가 선언하는 queryKey에 대응된다는 뜻일까? 이해가 잘 되지 않아서 내일 튜터님께 물어볼 예정!
쿼리는 서버로부터 데이터를 받아오는 프로미스 기반의 어떤 메서드와도 같이 사용할 수 있는데, 만약 데이터를 수정하고 싶은 거라면 쿼리 대신에 뮤테이션을 추천한다고 한다.
To subscribe to a query in your components or custom hooks, call the useQuery hook with at least:
- A unique key for the query
- A function that returns a promise that:
- Resolves the data, or
- Throws an error
커스텀 훅이나 컴포넌트에서 쿼리를 구독하려면 useQuery 를 쓰라고 한다.
그리고 적어도 두 개의 요소가 필요한데 고유한 키와 프로미스를 반환하는 함수다. 즉, 비동기 함수!
import { useQuery } from '@tanstack/react-query'
function App() {
const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}
The unique key you provide is used internally for refetching, caching, and sharing your queries throughout your application.
The query result returned by useQuery contains all of the information about the query that you'll need for templating and any other usage of the data:
우리가 제공하는 고유한 키는 앱 전반에 걸쳐서 데이터를 refetching하고 caching하고 sharing 하는데 사용된다고 한다. 그리고 useQuery 가 반환하는 값은 제가 데이터를 사용할 때 필요로 할 것들을 모두 담고 있다고 한다.
이번 주에 챌린지 분반 수업에서 다뤘던 { data, isLoading, isFetching } = useQuery({...}) 처럼 useQuery의 반환값으로 들어오는 객체에서 우리가 꺼내 쓸 수 있는 여러 도구들을 말하는 것 같다.
const result = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
The result object contains a few very important states you'll need to be aware of to be productive. A query can only be in one of the following states at any given moment:
isPendingorstatus === 'pending'- The query has no data yetisErrororstatus === 'error'- The query encountered an errorisSuccessorstatus === 'success'- The query was successful and data is available
useQuery의 반환값에는 우리의 생산성을 위해 필요할 중요한 상태들이 있는데 query는 각 상태들 중에서 한 가지 상태로만 존재할 수 있다고 한다.
isPending or status === 'pending' 은 query가 아직 데이터를 받아오지 않은 상태를 말하는 것 같다. 그 외에 데이터를 성공적으로 받아오거나 데이터를 받아오는 데에 실패했을 경우에는 isSuccess 나 isError 가 true 로 바뀌게 되는 것 같다.
아직은 잘 모르겠지만, 상태를 가지고 조건문을 작성해야 한다면 status 만 꺼내서 각각의 상태와 비교할 때 사용할 수 있을 것 같고, 한 가지 상태만 필요하면 isSomething 형태의 값을 꺼내서 사용하라는 용도로 이렇게 만들어진 것 같다.
// 이런 용도이지 않을까...?
const { status, isPending } = useQuery(options);
const someFunc = () => {
if(status === 'pending') {
...
} else if (status === 'error') {
...
}
}
if(isSuccess) return ...
Beyond those primary states, more information is available depending on the state of the query:
error- If the query is in anisErrorstate, the error is available via the error property.data- If the query is in anisSuccessstate, the data is available via the data property.isFetching- In any state, if the query is fetching at any time (including background refetching)isFetchingwill betrue.For most queries, it's usually sufficient to check for the
isPendingstate, then theisErrorstate, then finally, assume that the data is available and render the successful state:
query가 isError 와 isSuccess 상태일 때, 각각 error 와 data 속성을 사용할 수 있다고 한다.
isFetching 은 조금 더 체크해봐야겠지만 isPending 과 겹치지 않는 속성인가보다. isError 나 isSuccess 가 isPending 이후에만 존재하는 배타적인 속성이라면, isFetching 은 어떤 state 에서든 true 일 수 있는 속성이라고 한다.
function Todos() {
const { isPending, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
if (isPending) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error: {error.message}</span>
}
// We can assume by this point that `isSuccess === true`
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
If booleans aren't your thing, you can always use the status state as well:
function Todos() {
const { status, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
if (status === 'pending') {
return <span>Loading...</span>
}
if (status === 'error') {
return <span>Error: {error.message}</span>
}
// also status === 'success', but "else" logic works, too
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
TypeScript will also narrow the type of data correctly if you've checked for pending and error before accessing it.
위의 이야기가 처음에 말했던 isSomething 형식의 속성과 status 속성을 사용하는 예시인 것 같다. 타입스크립트는 아직 안배워서 무슨 이야기인지 정확히는 모르겠음!
타입스크립트에서는 자료의 타입을 모두 달아줘야 하는데 함수의 파라미터처럼 들어올 값이 아직 정해져 있지 않은 경우가 아니라, 데이터 자체에 접근하거나 데이터를 사용하는 경우에는 타입스크립트가 자동으로 타입을 달아주는 것으로 알고 있다. 그래서 자료에 접근하기 전에 pending 이나 error 를 확인하면 데이터의 유형, 아마도 자료형을 타입스크립트가 더 파악하기 쉽게 만들 수 있다는 뜻으로 이해했다. 이건 확실치 않음.
In addition to the status field, you will also get an additional fetchStatus property with the following options:
fetchStatus === 'fetching'- The query is currently fetching.fetchStatus === 'paused'- The query wanted to fetch, but it is paused. Read more about this in the Network Mode guide.fetchStatus === 'idle'- The query is not doing anything at the moment.
query의 반환값에는 status 뿐만이 아니라 fetchStatus 라는 속성도 있다고 한다. fetchStatus 역시도 status 처럼 세 가지 상태 중에서 한 가지 상태에 위치한다.
fetching 상태는 query가 현재 데이터를 가져오고 있는 중이라는 것을 의미한다.
paused 상태는 query가 데이터를 가져오려고 하지만 멈춰있는 상태라고 한다. Network Mode 를 더 읽어보라는 걸 보니까 아무래도 데이터를 받아오는 와중에 네트워크 연결에 실패하거나 네트워크 상의 이유로 멈춰있는 상태에 대해 이야기하는 것일 수도 있겠다.
idle 상태는 query가 아무것도 하고 있지 않은 상태를 의미한다. 내가 알기로 useQuery는 component가 mount되는 순간 실행된다는 것을 생각하면, 아마도 fetching이 완료된 상태를 이야기하는 것 같다.
만약 query가 실행될 때, 네트워크 에러가 지속되어 query에 default로 설정된 3번의 retry까지 실패하면 fetchStatus 는 paused 상태가 될까 idle 상태가 될까?
Background refetches and stale-while-revalidate logic make all combinations for status and fetchStatus possible. For example:
- a query in success status will usually be in idle fetchStatus, but it could also be in fetching if a background refetch is happening.
- a query that mounts and has no data will usually be in pending status and fetching fetchStatus, but it could also be paused if there is no network connection.
So keep in mind that a query can be in pending state without actually fetching data. As a rule of thumb:
- The status gives information about the data: Do we have any or not?
- The fetchStatus gives information about the queryFn: Is it running or not?
글을 읽으면서 “왜 상태가 두 개나 있지?”라는 궁금증이 들자마자 바로 설명해준다. 빠른 사람들!
status 는 데이터에 대한 정보를 제공한다. status 가 주목하는 것은 데이터를 받았는지 아닌지다.
fetchStatus 는 queryFn 에 대한 정보를 제공한다. fetchStatus 이 주목하는 것은 queryFn 이 실행중인지 아닌지다.
이미 데이터를 받아오는 데에 성공해서 status 가 success 상태라면 일반적으로 fetchStatus는 idle 상태이겠지만, 만약 background refetch 가 진행중이라면 status 가 success 인 상태에서도 fetchStatus 는 fetching 상태일 수 있다.
이처럼 status 와 fetchStatus 로 이원화된 query의 상태는 다양하고 복잡한 상황에서도, 우리의 로직을 우리가 원하는 조건에서 적절하게 실행할 수 있도록 도와준다. 이러한 상태 구성은 data의 refresh를 염두에 두고 query의 상태를 만들었기 때문이 아닌가 싶다.
데이터의 상태를 pending 에서 error 나 success 로 흘러가는 단방향적인 흐름이 아니라, 데이터의 수명 주기를 바탕으로 데이터가 순환하는 흐름을 고려했기 때문에 나올 수 있는 구성이 아닌가 생각한다.
status랑 fetchStatus의 차이를 제대로 알지 못했었는데, 왜 query의 상태를 두 가지로 나누어 보여주는 지 조금 더 잘 이해해볼 수 있는 기회였다. 이제 isLoading 말고도 다른 속성들도 써볼 수 있을 것 같다!