useQuery hook
는 서버 데이터를 GET 요청할 때 사용되는 hook
import { useQuery } from '@tanstack/react-query';
const { data, isLoading, error } = useQuery(queryKey, queryFn, Options);
queryKey
: 첫번째 인수는 ['key', ...deps]
배열을 주입합니다.queryFn
: 데이터를 fetching
하는 비동기 함수를 전달하며, Promise 객체를 반환하는 함수를 전달합니다.options
: 객체 타입으로 쿼리에 대한 옵션을 작성할 수 있습니다.react query는 Generic
을 많이 사용합니다.
function useGroups() {
return useQuery<Group[], Error>('groups', fetchGroups)
}
useQuery Hook의 정의
export function useQuery<
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey
>
Key | Value |
---|---|
TQueryFnData | queryFn 반환되는 타입 (ex Group[]) |
TError | queryFn에서 예상되는 오류 타입 (ex Error) |
TData | 데이터 프로퍼티가 최종적으로 보유되는 타입, queryFn이 반환하는 것과 다를 수 있으므로 선택 옵션을 사용하는 경우 해당 default : queryFn |
TQueryKey | queryFn에 전달된 queryKey를 사용하는 경우 해당 queryKey 타입 |
하나의 Generic
을 제공하는 경우 모든 Generic
을 제공해야 합니다.
Generic
을 제공하지 않았기 때문에 기본 값은 Group[]
이지만 선택자 (select)에서 number를 반환하기 때문에 타입 에러가 발생합니다.function useGroupCount() {
return useQuery<Group[], Error>('groups', fetchGroups, {
select: (groups) => groups.length,
// 🚨 Type '(groups: Group[]) => number' is not assignable to type '(data: Group[]) => Group[]'.
// Type 'number' is not assignable to type 'Group[]'.ts(2322)
})
}
➡ 단순하게 해결하기 위해서는 세번 째 제네릭을 추가합니다.
function useGroupCount() {
// ✅ fixed it
return useQuery<Group[], Error, number>('groups', fetchGroups, {
select: (groups) => groups.length,
})
}
Generic
을 수동으로 지정하지 않습니다.function fetchGroups(): Promise<Group[]> {
return axios.get('groups').then((response) => response.data)
}
// ✅ data will be `Group[] | undefined` here
function useGroups() {
return useQuery(['groups'], fetchGroups)
}
// ✅ data will be `number | undefined` here
function useGroupCount() {
return useQuery(['groups'], fetchGroups, {
select: (groups) => groups.length,
})
}
Generic
을 사용하지 않으면 오류가 unknown
으로 추론됩니다.
➡ 자바스크립트에서는 무엇이든 throw 할 수 있으며 Error 타입이 아니어도 됩니다.
react query는 Promise를 반환하는 기능을 담당하지 않기 때문에 어떤 타입의 오류가 발생하는지 알 수 없기 때문에 unknown 타입으로 반환됩니다.
💡 검사 인스턴스를 통해 타입을 좁힐 수 있습니다.
const groups = useGroups()
if (groups.error) {
// 🚨 this doesn't work because: Object is of type 'unknown'.ts(2571)
return <div>An error occurred: {groups.error.message}</div>
}
// ✅ the instanceOf check narrows to type `Error`
if (groups.error instanceof Error) {
return <div>An error occurred: {groups.error.message}</div>
}
data 또는 error와 같은 이름은 매우 보편적으로 수정할 가능성이 높음
➡ destructuring을 사용하지 않음
const { data, isSuccess } = useGroups()
if (isSuccess) {
// 🚨 data will still be `Group[] | undefined` here
}
const groupsQuery = useGroups()
if (groupsQuery.isSuccess) {
// ✅ groupsQuery.data will now be `Group[]`
}
enabled
옵션을 사용하면 useQuery를 동기적으로 사용할 수 있습니다.
id의 값이 정의되지 않을 수 있음 (undefined)
➡ enabled
옵션은 어떤 타입 좁힙도 수행하지 않음
function fetchGroup(id: number): Promise<Group> {
return axios.get(`group/${id}`).then((response) => response.data)
}
function useGroup(id: number | undefined) {
return useQuery(['group', id], () => fetchGroup(id), {
enabled: Boolean(id),
})
// 🚨 Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
// Type 'undefined' is not assignable to type 'number'.ts(2345)
}
💡 queryFn
에서 Promise를 reject하는 것이 가장 좋은 방법
중복되지만 명확하며 안전합니다.
function fetchGroup(id: number | undefined): Promise<Group> {
// ✅ check id at runtime because it can be `undefined`
return typeof id === 'undefined'
? Promise.reject(new Error('Invalid id'))
: axios.get(`group/${id}`).then((response) => response.data)
}
function useGroup(id: number | undefined) {
return useQuery(['group', id], () => fetchGroup(id), {
enabled: Boolean(id),
})
}
참조