๐ก 02. [์ํคํ ์ฒ] ํจ๊ณผ์ ์ธ Query Key ์ค๊ณ ์ํฌ์ต
๐ ๊ฐ์
์ ์ญ ์บ์๋ฅผ ํํธํ ์์ด ๊ณ์ธต์ ์ผ๋ก ์ค๊ณํ๋ ๊ธฐ๋ฒ๊ณผ, ์ค๋ฌด์ ๊ฝ์ธ Query Key Factory ํจํด(๋ฐ Query Options API)์ ๋์ ํ์ฌ ํ์ ์์ ์ฑ์ ํ๋ณดํฉ๋๋ค.
๐ ๋ชฉ์ฐจ
- โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "๋ฌดํจํ๋ฅผ ๋ ๋ ธ๋๋ฐ ์๋ฑํ ์ ๋ค์ด ์ฌ๋ผ์ ธ์..."
- ๐ค ์ ์์์ผ ํ๋๊ฐ: ๋ฌธ์์ด ์คํ๊ฐ ๋ถ๋ฅด๋ ์บ์ ๋ฌธ์ ์ํฉ
- 1. ๋ฐฐ์ด ํค์ ํฌํจ ๊ด๊ณ (Fuzzy Matching) ํด๋ถ
- 2. ์ค์ ์ ์ฉ: Query Key Factory ์ํคํ ์ฒ
- 3. ๊ถ๊ทน์ Next Step: Query Options API (v5)
- ๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
"์์ฒ ๋, ์๊น
invalidateQueries(['todos'])์น์ จ์ฃ ? ๋ฐฉ๊ธ ์์ ๋์ด ๋ณด๋ ํ๋ฉด ์บ์๊น์ง ์น ๋ ์๊ฐ์ต๋๋ค. ์ฟผ๋ฆฌ ํค ์คํ ๋ด์ จ๊ฑฐ๋ ์."
โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "๋ฌดํจํ๋ฅผ ๋ ๋ ธ๋๋ฐ ์๋ฑํ ์ ๋ค์ด ์ฌ๋ผ์ ธ์..."
(ํ์์ผ ์ ์ฌ ์ง์ , ๋ฒ๊ทธ ์ ๋ณด๋ฅผ ๋ฐ๊ณ ์ฐฝ๋ฐฑํด์ง ์์ฒ )
๐ฃ ์์ฒ : ๋ฆฌ๋ ๋, ์๋ฃ๋ ํ ์ผ ๋ชฉ๋ก์์ ์์ดํ ํ๋๋ฅผ ์ญ์ ํ๋๋ฐ ๋ฉ์ธ ํ๋ฉด์ ์งํ ์ค ๋ฆฌ์คํธ์ ์ด๋ฒ ๋ฌ ํต๊ณ ์บ์๊น์ง ๊ฐ์ด ๋ฌดํจํ๋์ด์. ํ๋ฉด์ ๋ง๊ฒ ๊ฐฑ์ ๋์ง๋ง, ์๋ฒ ๋ก๊ทธ๋ฅผ ๋ณด๋ ํ์ ์๋ ๋ฆฌํจ์นญ์ด ๋๋ฌด ๋ง์ด ์๊ฒผ์ต๋๋ค.
๐ฆ ์ํธ: ์ฝ๋ ์ค ๋ณด์ธ์. (ํ๋ฅํ๋ฅ)
queryClient.invalidateQueries({ queryKey: ['todos'] }) ์ด๋ ๊ฒ ํธ์ถํ์
จ๊ตฐ์.
์์ฒ ๋์ด ๋ชจ๋ ํ
๋ค์ ์ฟผ๋ฆฌ ํค๋ฅผ ['todos', 'done'], ['todos', 'in-progress'], ์ฌ์ง์ด ['todos', 'stats', 'monthly'] ์ด๋ ๊ฒ ์ค๊ตฌ๋๋ฐฉ ๋ฌธ์์ด๋ก ๋ฌ์๋์
จ์์์.
React Query์ invalidateQueries๋ ์ ๋ฌํ queryKey๋ฅผ prefix๋ก ์ผ์ ์ผ์นํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ฐพ์ต๋๋ค. ์ฆ ['todos']๋ฅผ ๋๊ธฐ๋ฉด ['todos', 'list'], ['todos', 'detail', id]์ฒ๋ผ ๊ทธ ์๋ ๊ณ์ธต์ ์ฟผ๋ฆฌ๋ ํจ๊ป ๋์์ด ๋ ์ ์์ต๋๋ค.
๐ฃ ์์ฒ : ํ! ๊ทธ๋ผ ์ง์ฐ๊ณ ์ถ์ '์๋ฃ๋ ๋ฆฌ์คํธ'๋ง ๋ฑ ๊ผฌ์ง์ด์ ์ง์ฐ๋ ค๋ฉด ์ด๋กํด์?
๐ฆ ์ํธ: ๊ทธ๋์ 5๋ ์ฐจ ์ค๋ฌด์ ์์์ "์ฟผ๋ฆฌ ํค ํฉํ ๋ฆฌ(Query Key Factory)" ๊ณ์ธตํ ์ํคํ ์ฒ ๋ฅผ ์ง๋ ๊ฒ๋ถํฐ ์ถ๋ฐํ๋ ๊ฒ๋๋ค. ์ค์ ํต์ ์๊ฐ ์์ผ๋ฉด ์ด๋ฐ ์คํ๊ฒํฐ ๋ฌดํจํ ํ์์ด ๋ฐ์ํฉ๋๋ค.
๐ค ์ ์์์ผ ํ๋๊ฐ: ๋ฌธ์์ด ์คํ๊ฐ ๋ถ๋ฅด๋ ์บ์ ๋ฌธ์ ์ํฉ
React Query์ ์ฝ์ด ์ฌ์ฅ์ QueryCache ๋ผ๋ ๊ฑฐ๋ํ ๊ธ๊ณ ์
๋๋ค.
๊ทธ ๊ธ๊ณ ์ ๋ฐฉ ๋ฒํธ๊ฐ ๋ฐ๋ก ์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ด๋ก ๋๊ฒจ์ฃผ๋ Query Key ์ฃ .
์ฌ๊ธฐ์ ๋ฐ์ํ๋ ๋ ๊ฐ์ง ์น๋ช ์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
- ํ์
์ ๋ถ์ฌ: ๊ฐ๋ฐ์ A๋
useQuery({ queryKey: ['todo', 1] })๋ผ๊ณ ์ง๊ณ , ๊ฐ๋ฐ์ B๋ ์ญ์ ์๋ฃ ํ ์๋ฑํ๊ฒqueryClient.invalidateQueries({ queryKey: ['todos', 1] })๋ผ๊ณ ์คํ๋ฅผ ๋ ๋๋ค. ์๋ฌด์ผ๋ ์ผ์ด๋์ง ์๊ณ ์ ์ ๋ ์์ํ ๋ก์ ํ๋ฉด์ ๋ด ๋๋ค. (TypeScript๋ ๋ฌธ์์ด ์คํ๋ ๋ชป ์ก์์ค๋๋ค!) - ๋ถ๋นํธ๋ฉ(Fuzzy Matching): ๋ฐฐ์ด์ ํฌํจ ๊ด๊ณ ์๋ฆฌ๋ฅผ ๋ชจ๋ฅด๋ฉด, ์์ฒ ์ด์ฒ๋ผ ์ค์๋ก ์ฑ ์ ์ฒด์ ๋คํธ์ํฌ๋ฅผ ์ฃ๋ค ์ด๊ธฐํ์์ผ๋ฒ๋ฆฌ๋ ๊ณผ๋ํ ์ฑ๋ฅ ์ ํ ๊ธ์ฆ์ด ๋ฐ์ํฉ๋๋ค.
์ด ์ฑํฐ์์๋ ์ ์ญ ์์ ๊ฐ์ฒด ๋ก ์ฟผ๋ฆฌ ํค๋ฅผ ์ค๊ณํ์ฌ ์คํ์ ๊ณผ๋ํ ๋ฌดํจํ ๋ฒ์๋ฅผ ์ค์ด๋ ๊ตฌ์กฐ๋ฅผ ๋ฐฐ์๋๋ค.
1. ๋ฐฐ์ด ํค์ ํฌํจ ๊ด๊ณ (Fuzzy Matching) ํด๋ถ
TkDodo์ ์ํฐํด #8์ ๋ช
์๋ ๊ฐ์ฅ ์ค์ํ ๋ฌดํจํ ๋ฃฐ์
๋๋ค.
"์์์๋ถํฐ ๊ณ์ธต(๋ฐฐ์ด ์์)์ด ์ผ์นํ๋ฉด ๋ฌดํจํ ๋์์ ํฌํจ๋ ์ ์๋ค!"
queryClient.invalidateQueries({ queryKey: ['todos'] }) ํ๋๋ฅผ ๋ฐ์ฌํ์ ๋,
- โ
['todos'](๋ง์ -> ๋ฌดํจํ) - โ
['todos', 'list', 'done'](todo๊ณ์ด ๋ง์ -> ๋ฌดํจํ ๋ฒ์ ์ ๋จ!) - โ
['todos', 'detail', 1](todo๊ณ์ด ๋ง์ -> ์ญ์ !) - โ
['users', 'todos'](ํ๋ฆผ, ๋งจ ์์ดusers์ -> ์ด๋ ค์ค)
๋ง์ฝ ์์ฒ ์ด๊ฐ "๋ฑ 'done' ์ํ์ธ ๋ฆฌ์คํธ๋ง" ์ฐ์ํ๊ฒ ์ง์ฐ๊ณ ์ถ์๋ค๋ฉด?
queryClient.invalidateQueries({ queryKey: ['todos', 'list', 'done'] })์ฒ๋ผ ๋ชฉ๋ก ๋ฒ์๋ฅผ ๋ ๊ตฌ์ฒด์ ์ผ๋ก ํํํด์ผ ๋ค๋ฅธ ํ์ ์บ์๋ค(['todos', 'detail', 1])์ ๊ฑด๋๋ฆฌ์ง ์์ ์ ์์ต๋๋ค.
2. ์ค์ ์ ์ฉ: Query Key Factory ์ํคํ ์ฒ
๋ฌธ์์ด ํ๋์ฝ๋ฉ์ ์์ฒ ๋ด์ํ๊ธฐ ์ํด ์ ์ญ ํฉํ ๋ฆฌ ํ์ผ(keys.ts ๋๋ queries.ts)์ ์์ํ๋ ๊ฐ์ฒด(Factory Pattern) ๋ฅผ ๋ง๋ญ๋๋ค.
// ๐ constants/queryKeys.ts
// ๐ ๋ชจ๋ ์ฟผ๋ฆฌ ํค๋ ์ด ๊ฐ์ฒด ํธ๋ฆฌ๋ฅผ ํตํด ์์ฑ๋๋ค! (๊ณต์ฅ)
export const todoKeys = {
// 1๋จ๊ณ: ๊ธฐ๋ณธ ๋๋ฉ์ธ ํค (๋๋ฉ์ธ ์ ์ฒด ๋ฌดํจํ๊ฐ ํ์ํ ๋๋ง ์ฌ์ฉ)
all: ['todos'] as const,
// 2๋จ๊ณ: ๋ฆฌ์คํธ ๊ณ์ด ๋ฌถ์ ํค
lists: () => [...todoKeys.all, 'list'] as const,
// 3๋จ๊ณ: ๊ตฌ์ฒด์ ์ธ ๊ฒ์/ํํฐ ๋ฆฌ์คํธ (์์ ํค๋ค์ ์ ๋ถ ํ๊ณ ๋ด๋ ค์ด)
list: (filters: string) => [...todoKeys.lists(), { filters }] as const,
// 2๋จ๊ณ: ๋จ๊ฑด(๋ํ
์ผ) ๊ณ์ด ๋ฌถ์ ํค
details: () => [...todoKeys.all, 'detail'] as const,
// 3๋จ๊ณ: ํน์ 1๊ฑด ๋ํ
์ผ
detail: (id: number) => [...todoKeys.details(), id] as const,
};์ด๋ ๊ฒ ๊ณต์ฅ์ ์ฐจ๋ ค๋๋ฉด, ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ(View, Hook) ์ชฝ ์ฝ๋๊ฐ 100% ์์ ํ๊ณ ์คํ ์๋ ์ฝ๋๋ก ์งํํฉ๋๋ค.
// 1๏ธโฃ ๋ฐ์ดํฐ ๊ฐ์ ธ์ฌ ๋
import { todoKeys } from '@/constants/queryKeys';
function TodoDetail({ id }: { id: number }) {
// ์คํ ๋ ํ๋ฅ 0%, id ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๋ฃ์ผ๋ฉด TypeScript๊ฐ ์ฆ์ ์๋ํฐ์์ ์๋ฌ ๋ฟ์!
const { data } = useQuery({
queryKey: todoKeys.detail(id),
queryFn: () => fetchTodoDetail(id),
});
}// 2๏ธโฃ ์ญ์ /์์ ์๋ฃ ํ ๋ฌดํจํ ํ ๋ (์ ๋ฐ ํ๊ฒฉ)
const deleteMutation = useMutation({
mutationFn: deleteTodo,
onSuccess: () => {
// ๐ฅ "todos ๋ํ
์ผ ์บ์๋ ๋ค ์ด๋ ค๋๊ณ , ๋ฆฌ์คํธ ๊ณ์ด ๋ ๋๋ง๋ง ์น ๋ค์ ํด!"
queryClient.invalidateQueries({ queryKey: todoKeys.lists() });
}
})3. ๊ถ๊ทน์ Next Step: Query Options API (v5)
์ํคํ
์ฒ์ ์์ฌ์ด ๋ง์ ์๋์ด๋ค์ ํ ํต ๋ ๋์๊ฐ๋๋ค.
ํค(queryKey) ๋ฐ๋ก, ํ์
(queryFn) ๋ฐ๋ก ๊ด๋ฆฌํ๋ ๊ฒ๋ ๊ท์ฐฎ์ต๋๋ค. ์ด์ฐจํผ ['todos', id] ๋ฉด ๋ฐ๋์ fetchTodoDetail(id) ํจ์๋ฅผ ํธ์ถํด์ผ ํ๋ ๊ฒ ํ์ฐ์ ์ธ ์ด๋ช
(Coupling)์ด์์์?
๊ทธ๋์ ์ต์ React Query v5 ์์๋ ์ด ๋์ ํ ๋ชธ์ผ๋ก ๋ฌถ์ด๋ฒ๋ฆฐ queryOptions ๊ธฐ๋ฐ์ ํฉํ ๋ฆฌ๊ฐ ํ์ค์ด ๋์์ต๋๋ค (TkDodo #24 ์ฐธ์กฐ). ์์ Basic 07๊ฐ์ ์ฌํ ๋ณต์ต์
๋๋ค.
import { queryOptions } from '@tanstack/react-query';
export const todoQueries = {
all: () => ['todos'],
// ํค + ํ์นญ ๋ก์ง + ๋ง๋ฃ๊ธฐ๊ฐ(staleTime) ๊น์ง ์์ํ ํ๋๋ก ๋ฌถ์ด๋ฒ๋ฆผ!
detail: (id: number) => queryOptions({
queryKey: [...todoQueries.all(), 'detail', id],
queryFn: () => axios.get(`/todos/${id}`).then(res => res.data as Todo),
staleTime: 5 * 60 * 1000,
}),
};// ๋ฐ์ดํฐ ์์ ๋ฌดํจํ ๋, ๊ทธ๋ฅ ํต์ง ์ต์
๊ฐ์ฒด๋ฅผ ๋ฐ์ฌํด ๋ฒ๋ฆฝ๋๋ค.
queryClient.invalidateQueries(todoQueries.detail(1));๋ฌธ์์ด ์คํ ๊ฐ๋ฅ์ฑ์ ์ค์ด๊ณ , ๊ฐ์ ํค๋ฅผ useQuery, prefetchQuery, invalidateQueries์์ ์ผ๊ด๋๊ฒ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
Q. ๋ค์๊ณผ ๊ฐ์ ๊ณ์ธต์ ๊ฐ์ง๋ ์ฟผ๋ฆฌ ํค ํฉํ ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ์ต๋๋ค. ํน์ Mutation ์ดํ์ ์๋์ invalidateQueries ๋ฅผ ์คํํ์ ๋, ๋ฌดํจํ(์ญ์ ๋ฐ ๋ฆฌํจ์น ๋งํน)๋ฅผ 'ํผํ๊ฒ ๋๋(์์กดํ๋)' ์บ์๋ ์ด๋ ๊ฒ์ผ๊น์?
queryClient.invalidateQueries({ queryKey: ['orders', 'shipping'] });- A)
['orders', 'shipping', 'list', { status: 'done' }] - B)
['orders', 'shipping', 12345] - C)
['orders', 'payment', 'list']
โ
์ ๋ต: C
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: React Query์ ๋ฌดํจํ๋ ๋ฐฐ์ด์ ์๋ถ๋ถ์ด ์ผ์นํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ฐพ๋ prefix ๊ธฐ๋ฐ fuzzy matching์ ์ฌ์ฉํฉ๋๋ค.
['orders', 'shipping']์ ๋๊ธฐ๋ฉด A์ B์ฒ๋ผ ๊ฐ์ prefix๋ฅผ ๊ฐ์ง ์ฟผ๋ฆฌ๋ ๋์์ด ๋๊ณ , C์ฒ๋ผ ๋ ๋ฒ์งธ ๊ณ์ธต์ด ๋ค๋ฅธ ์ฟผ๋ฆฌ๋ ๋์์์ ๋ฒ์ด๋ฉ๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, C๋ฒ ์บ์๋
orders๊น์ง๋ ๊ฐ์ง๋ง ๋ ๋ฒ์งธ ๊ณ์ธต์์shipping์ด ์๋๋ผpayment๋ก ๊ฐ๋ผ์ก์ฃ ? ๊ทธ๋์ ๋ฌดํจํ ๋ฒ์ ๋ฐ์ ๋จ์ต๋๋ค. ์ด๋ ๊ฒ ๊ณ์ธต(Hierarchy) ์์ ์ค๊ณ๊ฐ ๋ฌดํจํ ๋ฒ์๋ฅผ ๊ฒฐ์ ํฉ๋๋ค." - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ์์์๋ถํฐ ์คํ ๋ง๊ณผ ์์๊ฐ ์ผ์นํ๋ฉด ๋์, ์ค๊ฐ ๊ณ์ธต์ด ๊ฐ๋ผ์ง๋ฉด ๋์ ๋ฐ.
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. Query Key Factory๋ฅผ ๋์ ํ๋ ํต์ฌ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
โ ์ ๋ต: ๋๋ฉ์ธ๋ณ ํค ๊ณ์ธต์ ํ๊ณณ์์ ๋ง๋ค๊ณ ์ฌ์ฌ์ฉํด ์คํ๋ฅผ ์ค์ด๋ฉฐ ๋ฌดํจํ ๋ฒ์๋ฅผ ์์ธก ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ธฐ ์ํด์์ ๋๋ค.
๐ก ์์ธ ํด์ค:
๋ฌธ์์ด ๋ฐฐ์ด์ ์์ผ๋ก ๋ฐ๋ณตํ๋ฉด ['todo']์ ['todos'] ๊ฐ์ ๋ฏธ๋ฌํ ์ฐจ์ด๊ฐ ์บ์๋ฅผ ๊ฐ๋ผ๋์ต๋๋ค. Factory๋ ๋ฆฌ์คํธ, ์์ธ, ํํฐ ํค์ ๋ถ๋ชจยท์์ ๊ด๊ณ๋ฅผ ์ฝ๋๋ก ๊ณ ์ ํฉ๋๋ค. ํ ๋จ์ ์บ์ ์ค๊ณ์์๋ ์ด ์ผ๊ด์ฑ์ด ์ฑ๋ฅ๋ณด๋ค ๋จผ์ ์
๋๋ค.
Q2. invalidateQueries({ queryKey: ['orders'] })๊ฐ ๋ฐฐ์ด ํค์์ ์๋ํ๋ ๊ธฐ๋ณธ ์๋ฆฌ๋ ๋ฌด์์ธ๊ฐ์?
โ ์ ๋ต: ํค์ ์๋ถ๋ถ์ด ์ผ์นํ๋ ์ฟผ๋ฆฌ๋ฅผ ๋์์ผ๋ก ์ผ๋ prefix ๊ธฐ๋ฐ fuzzy matching์ ๋๋ค.
๐ก ์์ธ ํด์ค:
['orders', 'list']์ ['orders', 'detail', id]๋ ['orders']์ ํ์ ๋ฒ์๋ก ๋ณผ ์ ์์ต๋๋ค. ๋ฐ๋ฉด ['users', 'orders']๋ ์๋ถ๋ถ์ด ๋ค๋ฅด๋ฏ๋ก ๋์์ด ์๋๋๋ค. ๊ทธ๋์ ํค ๊ณ์ธต์ ์ด๋ป๊ฒ ์ค๊ณํ๋๋๊ฐ ๋ฌดํจํ ํญ์ ๊ฒฐ์ ํฉ๋๋ค.
Q3. ์์ฒ ์ด์ ํ ์คํธ ํ์: ๋๊ธ ์์ธ ์์ ํ ๋๊ธ ๋ชฉ๋ก์ ๊ฐฑ์ ํ๋ ๊ฒ์๊ธ ์์ธ ์บ์๋ ๊ฑด๋๋ฆฌ๊ณ ์ถ์ง ์์ต๋๋ค. ๋ฌด์์ ๋จผ์ ํ์ธํด์ผ ํ๋์?
โ ์ ๋ต: ๋๊ธ ๋๋ฉ์ธ์ key factory๊ฐ ๋ชฉ๋ก๊ณผ ์์ธ ๋ฒ์๋ฅผ ๋ถ๋ฆฌํด ๋ฌดํจํํ ์ ์๋ ๊ณ์ธต์ ์ ๊ณตํ๋์ง ํ์ธํฉ๋๋ค.
๐ก ์์ธ ํด์ค:
์ ๋ฐ ๋ฌดํจํ๋ invalidateQueries ํ ์ค์ ๋ฌธ์ ๊ฐ ์๋๋ผ ํค ์ค๊ณ์ ๊ฒฐ๊ณผ์
๋๋ค. commentKeys.lists()์ฒ๋ผ ๋ฒ์๋ฅผ ํํํ ์ ์์ด์ผ ๋ถํ์ํ ์บ์๊น์ง ํ๋ค์ง ์์ต๋๋ค. ์์ฒ ์ด ์ด์ ๋ฌดํจํ๋ฅผ "ํธ์ถํ๋ ๊ฒ"์ด ์๋๋ผ "๋ฒ์๋ฅผ ์ค๊ณํ๋ ๊ฒ"์ผ๋ก ์ดํดํด์ผ ํฉ๋๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ todoKeys.all, todoKeys.lists()์ฒ๋ผ ๋ถ๋ชจ ๋ฐฐ์ด์ ํ์ฅํด ๊ณ์ธต์ ๋ง๋ค๋ฉด ๋ฌดํจํ ๋ฒ์๋ฅผ ์ฝ๋๋ก ํํํ ์ ์๋ค๋ ๊ฑธ ๋ฐฐ์ ๋ค.
๐ก ์ค๋์ ๊ตํ: "ํ๋์ฝ๋ฉ ๋ฐฐ์ด ํค๋ฅผ ๋ฐ๋ณตํ์ง ๋ง๊ณ , key factory๋ก ๋๋ฉ์ธ๊ณผ ๋ฒ์๋ฅผ ์ด๋ฆ ๋ถ์ด์."
์๊น ์คํ ํ๋๋ก ์ ์ฒด ๋ฒ์๋ฅผ ๋ฌดํจํํ์ ๋ ์์ ๋์ ์๋ฒ ๋ก๊ทธ๊ฐ ํฌ๊ฒ ํ๋ค๋ ธ๋ค. ๋ด์ผ์ ์ฑ ๊ณณ๊ณณ์ ํฉ์ด์ง [] ๋ฆฌํฐ๋ด ํค๋ฅผ ์ฐพ์ queryOptions ํฉํ ๋ฆฌ๋ก ๋ชจ์์ผ๊ฒ ๋ค.