๐ป queryOptions & mutationOptions ์ฃผ์ ๋ด์ฉ
queryOptions์ mutationOptions๋ ์ฟผ๋ฆฌ/๋ฎคํ
์ด์
์ต์
์ ํ๊ณณ์ ๋ชจ์ ๋๊ณ , ์ฌ๋ฌ ๊ณณ์์ ํ์
์์ ํ๊ฒ ์ฌ์ฌ์ฉํ๊ธฐ ์ํ ํฌํผ์
๋๋ค.
๋ฐํ์์์๋ ์ธ์๋ก ๋ฐ์ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฐํํ๋ ํญ๋ฑ ํจ์์ด๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ์ ์ธ ๋ฐํ์ ๋น์ฉ์ด ์์ต๋๋ค. ์ฆ, ์จ์ ํ ํ์
์คํฌ๋ฆฝํธ๋ฅผ ์ํ ํฌํผ๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
์ง์ ๋ฒ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
queryOptions, infiniteQueryOptions: v5.0.0๋ถํฐ
mutationOptions: v5.82.0๋ถํฐ
queryOptions๊ฐ ํ์ํ ์ด์
queryOptions ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
queryOptions์ ํจ๊ป ์ฐ๋ API
์ปดํฌ๋ํธ ๋จ์ ์ต์
์ฌ์ ์
ํ์
์ด ๋ถ์ queryKey(DataTag)
initialData์ skipToken ์ค๋ฒ๋ก๋
Query Key Factory ํจํด
infiniteQueryOptions
mutationOptions
mutationOptions ์ฌ์ฉ ์ ์ฃผ์ํ ์
์ ๋ฆฌ
queryOptions๊ฐ ํ์ํ ์ด์
useQuery์ ์ต์
์ ์ธ๋ผ์ธ์ผ๋ก ์์ฑํ๋ฉด ํ์
์ถ๋ก ์ด ์์ฐ์ค๋ฝ๊ฒ ๋์ํฉ๋๋ค. ํ์ง๋ง ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฌ์ฉํ๋ ค๊ณ ์ต์
์ ๋ณ๋ ํจ์๋ก ๋ถ๋ฆฌํ๋ ์๊ฐ, queryKey๊ฐ ๋ค๊ณ ์๋ ๋ฐ์ดํฐ ํ์
์ ๋ณด๊ฐ ์ฌ๋ผ์ง๋๋ค.
// โ ์ผ๋ฐ ๊ฐ์ฒด๋ก ๋ถ๋ฆฌํ ๊ฒฝ์ฐ
const groupOptions = ( id : number ) => ( {
queryKey : [ "groups" , id ] ,
queryFn : ( ) => fetchGroups ( id ) ,
staleTime : 5 * 1000 ,
} ) ;
// useQuery์ data๋ ์ถ๋ก ๋์ง๋ง,
const { data } = useQuery ( groupOptions ( 1 ) ) ;
// ๋ช
๋ นํ API์์๋ queryKey์ ํ์
์ ๋ณด๊ฐ ์์ด unknown์ด ๋ฉ๋๋ค.
const groups = queryClient . getQueryData ( groupOptions ( 1 ) . queryKey ) ;
// ^? unknown
queryOptions๋ก ๊ฐ์ธ๋ฉด ๋ฐํ๋ queryKey๊ฐ queryFn์ ๋ฐํ ํ์
์ ํจ๊ป ๋ค๊ณ ๋ค๋๊ธฐ ๋๋ฌธ์, ๋ช
๋ นํ API์์๋ ์ ๋ค๋ฆญ์ ๋งค๋ฒ ๋ช
์ํ ํ์๊ฐ ์์ต๋๋ค.
// โ
queryOptions๋ก ๊ฐ์ผ ๊ฒฝ์ฐ
const groups = queryClient . getQueryData ( groupOptions ( 1 ) . queryKey ) ;
// ^? Group[] | undefined
queryOptions ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
queryOptions์๋ useQuery์ ๋๊ธธ ์ ์๋ ์ต์
์ ๊ทธ๋๋ก ๋๊ธธ ์ ์์ผ๋ฉฐ, queryKey๋ ํ์๊ฐ์
๋๋ค.
import { queryOptions } from "@tanstack/react-query" ;
export const groupOptions = ( id : number ) =>
queryOptions ( {
queryKey : [ "groups" , id ] ,
queryFn : ( ) => fetchGroups ( id ) ,
staleTime : 5 * 1000 ,
// ...options
} ) ;
์ธ์๊ฐ ํ์ ์๋ค๋ฉด ํจ์๋ก ๊ฐ์ธ์ง ์๊ณ ์์๋ก ์ ์ธํด๋ ๋ฉ๋๋ค.
export const groupsOptions = queryOptions ( {
queryKey : [ "groups" ] ,
queryFn : fetchGroups ,
} ) ;
queryOptions์ ํจ๊ป ์ฐ๋ API
queryOptions๊ฐ ๋ฐํํ ๊ฐ์ฒด๋ ์ฟผ๋ฆฌ ์ต์
์ ๋ฐ๋ ๋๋ถ๋ถ์ ํ
๊ณผ queryClient ๋ฉ์๋์ ๊ทธ๋๋ก ๋๊ธธ ์ ์์ต๋๋ค.
// ํ
useQuery ( groupOptions ( 1 ) ) ;
useSuspenseQuery ( groupOptions ( 1 ) ) ;
useQueries ( {
queries : [ groupOptions ( 1 ) , groupOptions ( 2 ) ] ,
} ) ;
// queryClient ๋ฉ์๋
queryClient . prefetchQuery ( groupOptions ( 1 ) ) ;
queryClient . fetchQuery ( groupOptions ( 1 ) ) ;
queryClient . ensureQueryData ( groupOptions ( 1 ) ) ;
queryKey๋ง ํ์ํ ๋ฉ์๋์๋ .queryKey๋ฅผ ๊บผ๋ด์ ๋๊ฒจ์ค๋๋ค.
queryClient . setQueryData ( groupOptions ( 1 ) . queryKey , newGroups ) ;
queryClient . invalidateQueries ( { queryKey : groupOptions ( 1 ) . queryKey } ) ;
queryClient . cancelQueries ( { queryKey : groupOptions ( 1 ) . queryKey } ) ;
์ปดํฌ๋ํธ ๋จ์ ์ต์
์ฌ์ ์
๊ณตํต ์ต์
์ ๊ทธ๋๋ก ๋๊ณ , ์ปดํฌ๋ํธ์์ ํ์ํ ์ต์
๋ง ๋ฎ์ด์ธ ์ ์์ต๋๋ค. ๋ํ์ ์ผ๋ก ์ปดํฌ๋ํธ๋ง๋ค ๋ค๋ฅธ select ํจ์๋ฅผ ๋ถ์ด๋ ํจํด์ด ์์ต๋๋ค.
const { data } = useQuery ( {
...groupOptions ( 1 ) ,
select : ( data ) => data . map ( ( group ) => group . name ) ,
} ) ;
/**
* ํ์
์ถ๋ก ์ด ์ ์ง๋๋ฏ๋ก data๋ select์ ๋ฐํ ํ์
์ด ๋ฉ๋๋ค.
* data: string[] | undefined
*/
ํ์
์ด ๋ถ์ queryKey(DataTag)
queryOptions๊ฐ ๋ฐํํ๋ queryKey์๋ DataTag๋ผ๋ ํ์
์ ๋ณด๊ฐ ํจ๊ป ๋ถ์ต๋๋ค. ๋๋ถ์ queryClient์ ๋ช
๋ นํ API์์ ์ ๋ค๋ฆญ ์์ด๋ ํ์
์ด ์ถ๋ก ๋ฉ๋๋ค.
// โ
์ ๋ค๋ฆญ์ ๋ช
์ํ์ง ์์๋ ๋ฉ๋๋ค.
const groups = queryClient . getQueryData ( groupsOptions . queryKey ) ;
// ^? Group[] | undefined
// โ ์ง์ ๋ฐฐ์ด์ ๋๊ธฐ๋ฉด ๋งค๋ฒ ์ ๋ค๋ฆญ์ ๋ช
์ํด์ผ ํฉ๋๋ค.
const groups2 = queryClient . getQueryData < Group [ ] > ( [ "groups" ] ) ;
setQueryData์ updater ํจ์๋ ์ด์ ๋ฐ์ดํฐ ํ์
์ ์ถ๋ก ํฉ๋๋ค.
queryClient . setQueryData ( groupsOptions . queryKey , ( oldData ) => {
// oldData: Group[] | undefined
return oldData ? [ ...oldData , newGroup ] : oldData ;
} ) ;
๐ก ๋จ, getQueriesData๋ ์๋ก ๋ค๋ฅธ ํ์
์ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํํ๊ธฐ ๋๋ฌธ์ ํ์
์ถ๋ก ์ด ๋์ํ์ง ์์ต๋๋ค. ์ด๋๋ ์ ๋ค๋ฆญ์ ์ง์ ๋ช
์ํด์ผ ํฉ๋๋ค.
const entries = queryClient . getQueriesData < Group [ ] > ( {
queryKey : groupsOptions . queryKey ,
} ) ;
// ^? Array<[QueryKey, Group[] | undefined]>
initialData์ skipToken ์ค๋ฒ๋ก๋
initialData๋ฅผ ํจ๊ป ๋๊ธฐ๋ฉด data๊ฐ undefined๊ฐ ๋์ง ์๋ ์ค๋ฒ๋ก๋๊ฐ ์ ํ๋ฉ๋๋ค. ์ฆ, ์ปดํฌ๋ํธ์์ data๋ฅผ ์ต์
๋ ์ฒด์ด๋ ์์ด ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
export const postsOptions = queryOptions ( {
queryKey : [ "posts" ] ,
queryFn : fetchPosts ,
initialData : [ ] ,
} ) ;
function Posts ( ) {
const { data, isError, error } = useQuery ( postsOptions ) ;
return (
< div >
{ isError ? < span > Error: { error . message } </ span > : null }
< ul >
{ /* data: Post[] (undefined๊ฐ ์๋๋๋ค) */ }
{ data . map ( ( post ) => (
< li key = { post . id } > { post . title } </ li >
) ) }
</ ul >
</ div >
) ;
}
ํน์ ์กฐ๊ฑด์์๋ง ์ฟผ๋ฆฌ๋ฅผ ์คํํด์ผ ํ๋ค๋ฉด queryFn์ skipToken์ ๋๊ธธ ์ ์์ต๋๋ค. enabled: false์ ๋น์ทํ๊ฒ ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ง ์์ง๋ง, queryFn์ ์ธ์๊ฐ undefined์ผ ์ ์๋ ์ํฉ์ ํ์
๋ ๋ฒจ์์ ์์ ํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
import { queryOptions , skipToken , useQuery } from "@tanstack/react-query" ;
export const postOptions = ( postId : number | undefined ) =>
queryOptions ( {
queryKey : [ "post" , postId ] ,
queryFn : postId != null ? ( ) => fetchPost ( postId ) : skipToken ,
} ) ;
function Post ( { postId } : { postId : number | undefined } ) {
const { data, isLoading, isError, error } = useQuery ( postOptions ( postId ) ) ;
if ( postId == null ) return "Select a post" ;
if ( isLoading ) return "Loading..." ;
if ( isError ) return < span > Error: { error . message } </ span > ;
return < h1 > { data ?. title } </ h1 > ;
}
queryOptions๋ฅผ ๋ชจ์ ๋๋ฉด ๋ณ๋์ query key factory ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด๋ ๋๋ฉ์ธ๋ณ ์ฟผ๋ฆฌ๋ฅผ ํ๊ณณ์์ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
import { queryOptions } from "@tanstack/react-query" ;
export const postQueries = {
all : ( ) =>
queryOptions ( {
queryKey : [ "posts" ] ,
queryFn : fetchPosts ,
} ) ,
detail : ( id : number ) =>
queryOptions ( {
queryKey : [ "posts" , id ] ,
queryFn : ( ) => fetchPost ( id ) ,
staleTime : 60 * 1000 ,
} ) ,
} ;
// ์ฌ์ฉ ์์
useQuery ( postQueries . detail ( 1 ) ) ;
queryClient . prefetchQuery ( postQueries . detail ( 2 ) ) ;
// ["posts"]๋ฅผ ํฌํจํ๋ ๋ชจ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฌดํจํ๋ฉ๋๋ค.
queryClient . invalidateQueries ( { queryKey : postQueries . all ( ) . queryKey } ) ;
๋ฌดํ ์ฟผ๋ฆฌ์๋ infiniteQueryOptions๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ฌ์ฉ๋ฒ์ queryOptions์ ๋์ผํ๋ฉฐ, initialPageParam๊ณผ getNextPageParam์ ํจ๊ป ์ ์ํ ์ ์์ต๋๋ค.
import { infiniteQueryOptions , useInfiniteQuery } from "@tanstack/react-query" ;
export const colorsOptions = ( ) =>
infiniteQueryOptions ( {
queryKey : [ "colors" ] ,
queryFn : ( { pageParam } ) => fetchColors ( pageParam ) ,
initialPageParam : 1 ,
getNextPageParam : ( lastPage , allPages ) => {
return allPages . length < 4 ? allPages . length + 1 : undefined ;
} ,
} ) ;
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery ( colorsOptions ( ) ) ;
v5.82.0๋ถํฐ ๋ฎคํ
์ด์
์๋ ๋์ผํ ํฌํผ์ธ mutationOptions๊ฐ ์ถ๊ฐ๋์ต๋๋ค. useMutation์ ๋๊ธธ ์ ์๋ ์ต์
์ ๊ทธ๋๋ก ๋๊ธธ ์ ์์ต๋๋ค.
queryOptions์ ๋ฌ๋ฆฌ ์ถ๊ฐ์ ์ธ ํ์
์ ๋ณด๋ฅผ ๋ถ์ด์ง ์๊ณ ๋ฐ์ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฐํํฉ๋๋ค. ์ฆ, ์ต์
๊ฐ์ฒด์ ๋ํ ํ์
๊ฒ์ฌ์ ์๋ ์์ฑ์ ๋ฐ๊ธฐ ์ํ ํฌํผ์
๋๋ค.
1. mutationKey๊ฐ ์๋ ํํ
useMutationState๋ ํํฐ๋ก ๋ฎคํ
์ด์
์ ์ฐพ์ ์ธ ์ผ์ด ์๋ค๋ฉด mutationKey๋ฅผ ์๋ตํ ์ ์์ต๋๋ค.
import { mutationOptions , useMutation } from "@tanstack/react-query" ;
export const createPostOptions = mutationOptions ( {
mutationFn : createPost ,
} ) ;
function CreatePost ( ) {
const { mutate } = useMutation ( createPostOptions ) ;
return < button onClick = { ( ) => mutate ( { title : "Hello" } ) } > Create</ button > ;
}
2. mutationKey๊ฐ ์๋ ํํ
mutationKey๋ฅผ ์ง์ ํ๋ฉด ๋์ค์ useMutationState, useIsMutating ๋ฑ์ผ๋ก ํด๋น ๋ฎคํ
์ด์
์ ์ํ๋ฅผ ์กฐํํ ์ ์์ต๋๋ค.
import { mutationOptions , useMutationState } from "@tanstack/react-query" ;
export const createPostOptions = mutationOptions ( {
mutationKey : [ "posts" , "create" ] ,
mutationFn : createPost ,
} ) ;
// ์ ์ญ "์ ์ฅ ์ค..." ์ธ๋์ผ์ดํฐ
function SavingIndicator ( ) {
const isCreatingPost =
useMutationState ( {
filters : {
mutationKey : createPostOptions . mutationKey ,
status : "pending" ,
} ,
} ) . length > 0 ;
return isCreatingPost ? < span > Savingโฆ</ span > : null ;
}
์ต์
๊ฐ์ฒด ์์ฒด๋ฅผ ํํฐ๋ก ๋๊ธธ ์๋ ์์ต๋๋ค.
useIsMutating ( createPostOptions ) ;
queryClient . isMutating ( createPostOptions ) ;
mutationOptions ์ฌ์ฉ ์ ์ฃผ์ํ ์
mutationOptions๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฐํํ๊ธฐ ๋๋ฌธ์, ์คํ๋ ๋๋ก ์ฝ๋ฐฑ์ ๋ง๋ถ์ด๋ฉด ์๋ณธ ์ฝ๋ฐฑ์ด ๋ฎ์ด์จ์ง๋๋ค.
export const createPostOptions = mutationOptions ( {
mutationFn : createPost ,
onSuccess : ( ) => console . log ( "A" ) ,
} ) ;
useMutation ( {
...createPostOptions ,
onSuccess : ( ) => console . log ( "B" ) , // โ "A"๋ ์คํ๋์ง ์์ต๋๋ค.
} ) ;
์๋ณธ ์ฝ๋ฐฑ์ ํจ๊ป ์คํํ๊ณ ์ถ๋ค๋ฉด ์ง์ ํธ์ถํด ์ค์ผ ํฉ๋๋ค.
useMutation ( {
...createPostOptions ,
onSuccess : ( data , variables , context ) => {
createPostOptions . onSuccess ?.( data , variables , context ) ;
console . log ( "B" ) ;
} ,
} ) ;
๐ก ๋ ๊ถ์ฅ๋๋ ๋ฐฉ๋ฒ์ ๊ณตํต ๋ก์ง๊ณผ ์ปดํฌ๋ํธ ์ ์ฉ ๋ก์ง์ ๋ถ๋ฆฌํ๋ ๊ฒ์
๋๋ค.
์ฟผ๋ฆฌ ๋ฌดํจํ์ฒ๋ผ ํญ์ ์คํ๋์ด์ผ ํ๋ ๋ก์ง์ mutationOptions(= useMutation)์ Callback์ ๋ก๋๋ค.
๋ฆฌ๋ค์ด๋ ์
์ด๋ ํ ์คํธ์ฒ๋ผ ํ๋ฉด๋ง๋ค ๋ค๋ฅธ ๋ก์ง์ mutate์ Callback์ผ๋ก ๋๊น๋๋ค.
์์ธํ ๋ด์ฉ์ useMutation callback๊ณผ mutate callback์ ์ฐจ์ด ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์.
export const createPostOptions = mutationOptions ( {
mutationFn : createPost ,
onSuccess : ( ) => {
// ๊ณตํต ๋ก์ง
queryClient . invalidateQueries ( { queryKey : postQueries . all ( ) . queryKey } ) ;
} ,
} ) ;
const { mutate } = useMutation ( createPostOptions ) ;
mutate ( newPost , {
// ์ปดํฌ๋ํธ ์ ์ฉ ๋ก์ง
onSuccess : ( ) => navigate ( "/posts" ) ,
} ) ;
queryOptions
mutationOptions
์ง์ ๋ฒ์
v5.0.0
v5.82.0
ํ์ ์ต์
queryKey
์์(์ค๋ฒ๋ก๋์ ๋ฐ๋ผ mutationKey)
๋ฐํ ๊ฐ
์ต์
๊ฐ์ฒด + ํ์
์ด ๋ถ์ queryKey(DataTag)
๋ฐ์ ์ต์
๊ฐ์ฒด ๊ทธ๋๋ก
์ฃผ์ ์ฌ์ฉ์ฒ
useQuery, useSuspenseQuery, useQueries, queryClient.*
useMutation, useMutationState, useIsMutating
๋ฐํ์ ๋์
์ธ์๋ฅผ ๊ทธ๋๋ก ๋ฐํ
์ธ์๋ฅผ ๊ทธ๋๋ก ๋ฐํ
๋ ํฌํผ ๋ชจ๋ ๋ฐํ์ ๋์์ ์๊ณ ํ์
์ถ๋ก ๊ณผ ์ต์
์ฌ์ฌ์ฉ์ ์ํด ์กด์ฌํฉ๋๋ค.
์ต์
์ ๋ณ๋ ํ์ผ๋ก ๋ถ๋ฆฌํด ์ฌ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ๋์
๋น์ฉ์ด ๊ฑฐ์ ์์ผ๋ฏ๋ก ์ ๊ทน์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.