๋ฌธ์
Tanstack Query๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋ชจ๋ ์ฌ๋๋ค์ด ํ ๋ฒ์ฉ์ ๊ฒช๋ ๋ถํธํจ์ด๋ค.
์ด ์ฟผ๋ฆฌํค๋ฅผ ๋ค ๊ธฐ์ตํด๋์ผ ํด?
// ๋ฐ์ดํฐ ์กฐํ (useQuery)
const {
data: users,
isLoading,
error,
isError,
refetch
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5๋ถ๊ฐ fresh ์ํ ์ ์ง
cacheTime: 10 * 60 * 1000, // 10๋ถ๊ฐ ์บ์ ์ ์ง
retry: 3, // ์คํจ์ 3๋ฒ ์ฌ์๋
refetchOnWindowFocus: false, // ์๋์ฐ ํฌ์ปค์ค์ refetch ๋นํ์ฑํ
});์ค์ ๋ก ์ฐ๋ฆฌ๋ Tanstack Query๋ฅผ ํตํด์ ์ฟผ๋ฆฌ ํค๋ฅผ ๊ด๋ฆฌํ ๋, ๋ฌธ์์ด์ ๊ทธ๋๋ก ๋ฃ๊ธฐ๋ ํ๋ค. ํ์ง๋ง ์ด๋ ์ ์ ๊ด๋ฆฌํ๋ ์ฟผ๋ฆฌ ํค๊ฐ ๋ง์์ง๋ฉด ๋ง์์ง์๋ก ๋ด๊ฐ ์ด๋ค ์ฟผ๋ฆฌ ํค๋ฅผ ์ผ๋์ง๋ ๊น๋จน๊ณ ๋ค๋ฅธ ์ฟผ๋ฆฌ ํค๋ฅผ ์ฌ์ฉํ ์๋ ์์ผ๋ฉฐ, ์ฌํ ๊ฒฝ์ฐ ๋ฉ๋ชจ์ฅ์ ์ฌ์ดํธ ๋น๋ฐ๋ฒํธ ๊ธฐ๋กํด ๋๋ฏ์ด ๋ฐ๋ก ๊ธฐ๋กํด๋๊ธฐ๋ ํ๋ค. ํ์ง๋ง ์ด๋ฌํ ๋ฐฉ์์ ๊ฒฝ์ฐ ์ ์ ์ ์ง๋ณด์ํ๊ธฐ๊ฐ ๋ถํธํด์ง๋ค๋ ๋จ์ ์ด ์๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด๋ฅผ ์ด๋ป๊ฒ ์ฐ์ํ๊ฒ ๊ด๋ฆฌํ ์ ์์๊น?
https://tkdodo.eu/blog/effective-react-query-keys
Tanstack-query์ ๋ฉ์ธํ
์ด๋์ธ Dominik๋ผ๋ ๋ถ์ ์ด๋ฌํ ๋ฌธ์ ์ ๋ํด์ Query Key๋ฅผ ๊ตฌ์กฐํํ๋
Query Key Factory๋ฅผ ์ ์ํ๋ค.
const todoKeys = {
all: ['todos'] as const,
lists: () => [...todoKeys.all, 'list'] as const,
list: (filters: string) => [...todoKeys.lists(), { filters }] as const,
details: () => [...todoKeys.all, 'detail'] as const,
detail: (id: number) => [...todoKeys.details(), id] as const,
}์ด๋ ๊ฒ ์ฟผ๋ฆฌ ํค๋ฅผ ์์ฑํ ๊ฒฝ์ฐ, ์ฟผ๋ฆฌ ํค๋ฅผ ํ๋ฒ์ ๊ด๋ฆฌํ ์ ์์ ๋ฟ๋ง ์๋๋ผ, fuzzy matching์ ํ๋ tanstack query ์ ์ฑ๊ฒฉ์ ๋ง๊ฒ ํ์ฉํ ์๋ ์๋ค.
TanStack Query์์์ Fuzzy Matching๋
TanStack Query์์ fuzzy Matching์ ์๋ฏธ ๊ทธ๋๋ก โํ๋ฆฟํ ๋งค์นญโ์ด๋ค. ๋ฌด์กฐ๊ฑด ๋๊ฐ์ ์ฟผ๋ฆฌ์ ๋ํด์ ๋งค์นญํ๋ ๊ฒ์ด ์๋๋ผ, ํ๋ฆฟํ๊ฒ ๋ณด๊ณ ๊ฐ์๋ณด์ด๋ ์ฟผ๋ฆฌ์ ๋ํด์๋ ๋งค์นญ์ํค๋ ๊ตฌ์กฐ์ด๋ค.
๋ฐ๋ ๊ฐ๋
์ผ๋ก exact matching(์ ํํ ๋งค์นญ) ์ด ์๋ค.
ํฌ๋ฏธํ๋ค๋ ์๋ฏธ๊ฐ ๋ฌด์จ ์๋ฏธ์ธ์ง ๊ฐ์ด ์ ๋๋ก ์ค์ง ์์ ์๋ ์๋ค. ์ด๋ด ๋๋ ๋ถ๋ถ์ ์ธ ๊ณตํต์ ์ฟผ๋ฆฌ์ ๋ํ ๋งค์นญ์ ๋๋ผ๊ณ ์๊ฐํ๋ฉด ์ดํด๊ฐ ํธํ๋ค.
์์ ์ค๋ ๊ณตํต์ ์ธ ์ฟผ๋ฆฌ๊ฐ ์๋ค๋ฉด, ๊ทธ ๋ค์ ์ฟผ๋ฆฌ๋ค ๋ํ ๋ชจ๋ ๋งค์นญ์ ๊ฑธ๋ฆฌ๋ ์ฟผ๋ฆฌ๊ฐ ๋๋ค.
// ๋ถ๋ถ ๋งค์นญ (TanStack Query ๊ธฐ๋ณธ ๋ฐฉ์)
['user'] โ ['user', 1], ['user', 1, 'profile'] ๋งค์น๋จ
// ์ ํํ ๋งค์นญ
['user', 1] โ ์ ํํ ['user', 1]๋ง ๋งค์น๋จ
// ์กฐ๊ฑด๋ถ ๋งค์นญ
predicate: (query) => query.state.isStale๋ค๋ฅธ ๋ฐฉ์๋ ์์ง๋ง, ์ด๋ฒ์ ํนํ fuzzy Matching์ ๋ํด์ query key factory๋ฅผ ์ฌ์ฉํด์ ํด๋ณด๋ ค๊ณ ํ๋ค.
Query Key Factory๋ก ๋ณด์ํ๊ธฐ
๋ ๋ํ Query Key Factory๊ฐ ํ ๊ณณ์์ ์ฟผ๋ฆฌ ํค๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ ํจ์ฌ ์ ์ง๋ณด์์ฑ์ด ์ข์ผ๋ฉฐ, ๋ง์ฝ์ ๋ชจ๋ ์ฟผ๋ฆฌ๋ค์ ์ด๊ธฐํ์ํค๊ณ ์ถ์ ๋ ์ ์์์ all() ์ด๋ list(filter) ์ ๊ฐ์ด ํน์ ์ฟผ๋ฆฌ ํค ๋ฒ์์ ๋ฐ๋ผ์ invalidate ์ํฌ ์ ์๋ ๋ก์ง ๋ํ ๊ฐํธํ๊ฒ ๊ด๋ฆฌํ ์ ์๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฐฉ์์ ์ฑํํ๋ค.
const interviewKeys = {
all: ["interview"],
byInterviewId: (id: number): QueryKey => [...interviewKeys.all, id],
byInterviewIdAndQuestionId: (id: number, questionId: number) =>
[...interviewKeys.byInterviewId(id), questionId],
};๊ทธ๋ผ ๋ด๊ฐ ํ์ฌ ๊ด๋ฆฌํ๊ณ ์๋ Interview๋ผ๋ ๋๋ฉ์ธ์ ๋ํด์ ํค๋ ์ด๋ ๊ฒ ๊ด๋ฆฌํ ์ ์๋ค. ํ์ง๋ง ์ด ์ฟผ๋ฆฌ ํค๋ ํ์ ์ ๋ํด์ ์ ํํ ๋ช ์๋์ด์์ง ์๊ธฐ ๋๋ฌธ์ ๋ฆฐํธ ์๋ฌ๊ฐ ๋ด๋ค. ๋ฐ๋ผ์ ๊ธฐ์กด์ ์ฟผ๋ฆฌ ํค ํฉํ ๋ฆฌ๋ฅผ ์กฐ๊ธ ๊ณ ์น๋ฉด์ ํ์ ์ ๋ณด์ํ๋ค.
์ฟผ๋ฆฌ ํค ๋งค์นญ๊ณผ ํ์ ์์ ์ฑ ์ถ๊ฐํ๊ธฐ
type QueryKey = readonly (string | number)[];
type QueryKeyFactory<T> = {
readonly all: QueryKey;
} & {
[K in keyof T]: T[K] extends (...args: any[]) => QueryKey
? (...args: Parameters<T[K]>) => QueryKey
: QueryKey;
};
type InterviewMethods = {
byInterviewId: (id: number) => QueryKey;
byInterviewIdAndQuestionId: (id: number, questionId: number) => QueryKey;
};
const interviewKeys: QueryKeyFactory<InterviewMethods> = {
all: ["interview"] as const,
byInterviewId: (id: number): QueryKey => [...interviewKeys.all, id] as const,
byInterviewIdAndQuestionId: (id: number, questionId: number): QueryKey =>
[...interviewKeys.all, id, questionId] as const,
};์ฟผ๋ฆฌ ํค์ ๊ฒฝ์ฐ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ all์ readonly๋ก ํ์๊ณ , stringํน์ number ํ์
๋ง ์ฐ๋ Query Key์ ๊ฒฝ์ฐ๋ union ํ์
์ผ๋ก ํด์ฃผ๊ณ , QueryKeyFactory ์ ๊ฒฝ์ฐ์๋ ์ ๋ค๋ฆญ์ ๋ฐ๋๋ก ํ์๋ค.
์ ๋ค๋ฆญ์ ํ ๊ฐ์ฒด๋ก ๋์ด ์์ผ๋ฉฐ, T[K] ๊ฐ ํจ์๋ก ๋์ด ์์ผ๋ฉด ๊ทธ๋ฅ ๊ทธ๋๋ก ๋ด๋ณด๋ด๊ณ , ํด๋น ๊ฐ์ฒด์ ํค๊ฐ๊ณผ ํด๋น ํค๊ฐ์ ๋์๋๋ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์์ QueryKey๋ฅผ ๋ฐํํ๋๋ก ํ์
์ ๋ณด์ํ๋ค.