리액트(TanStack) 쿼리를 사용할때마다, data가 항상 data의 타입 | undefined
로 추론되어 일관적인 타입 적용이 안 되고, type assertion으로 해결하고 있어 타입 안정성이 보장되지 않는 듯 하여 작성하게 되었습니다
TanStack Query 5버전, TS 4.7버전 이상을 기준으로 하고 있습니다
TanStack Query의 개념에 대해서는 다루지 않습니다
const { data , isSuccess } = useQuery({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
})
const data : number | undefined
이 예시에서 알 수 있듯이, queryFn이 반환하는 값의 타입에 따라 데이터의 타입을 추론해준다
xx | undefined
으로 추론이 된다undefined
를 제거하기 위해서는 여러 방법이 있지만, 조금 안전하게 타입을 지정할 수 있는 방법을 채택하는 것이 좋음React Query uses a discriminated union type for the query result, discriminated by the status field and the derived status boolean flags. This will allow you to check for e.g. success status to make data defined
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
function area(s: Shape) {
if (s.kind === "circle") {
return Math.PI * s.radius * s.radius;
} else if (s.kind === "square") {
return s.x * s.x;
} else {
return (s.x * s.y) / 2;
}
}
이 예시처럼 if문으로 s.kind의 값(타입)을 좁혀놓으면, 알아서 타입을 식별해서 s.kind뿐 아니라 s.x
, s.radius
등도 타입을 추론해준다
const { data , isSuccess } = useQuery({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
})
if(isSuccess){
data //if문 블럭 안에서는 data : number
}
//여기서는 data : number | undefined
isSuccess
인 경우에는number
로만 추론하는 것을 알 수 있다
하지만 if문 밖을 벗어날경우 동일하다
const { data , isSuccess } = useQuery({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
select : (data) => {
// 해당 블럭 안에서는 data : number
}
})
undefined
는 사라지지 않음const { data } = useSuspenseQuery({
queryKey: ["todos"],
queryFn: () => Promise.resolve(5)
})
suspense의 개념을 알고 있다면 왜 data가 undefined로 추론이 안 되는지 알기 쉽다.
useSuspenseQuery
대신 suspense : true
를 사용하는 3~4버전의 경우에는 적용되지 않음
const { data , isSuccess } = useQuery({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
seleect : (data) => {
// 해당 블럭 안에서는 data : number
}
})
if(!data){
throw new Error()
}
//이 이후부터 data : number
아예 data가 undefined일 경우를 Error로 판단하여 던지는 방법도 있다.
이렇게 되면 이후에 사용되는 모든 data 변수는 하나의 타입으로만 추론이 된다
const { data , error } = useQuery({//const error : Error | null
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
})
3,4 버전에서는 error의 default type
이 unknown
이었지만, 5버전 부터는 Error
타입으로 추론된다
data와 비슷하게 없을 경우가 추가되어 Error | null
로 추론된다.
error가 Error
타입이 아닌, 특정 타입(AxiosError라던가, 커스텀 에러 객체라던가..)을 지정하고 싶다면 useQuery의 제네릭을 이용하면 된다.
export function useQuery<
TQueryFnData,
TError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey
>
여기서 두번째 제네릭을 지정해주면 된다
null
type이 없어지는건 아니다const {data,error} = useQuery<number,AxiosError>
//const error : AxiosError | null