[๐ค] React Query (TanStack Query) ์ฌํ: ๋ฐ์ดํฐ ํ์นญ, ์บ์ฑ, ๋๊ธฐํ ์ ๋ต์ผ๋ก ์น ์ฑ ์ฑ๋ฅ ๊ทน๋ํํด์
React Query (TanStack Query)๋ฅผ ํ์ฉํ์ฌ ๋ณต์กํ ์๋ฒ ์ํ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ , ์ง๋ฅ์ ์ธ ์บ์ฑ๊ณผ ์๋ ๋๊ธฐํ ์ ๋ต์ผ๋ก ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด์. useQuery, useMutation, useInfiniteQuery ๋ฑ ํต์ฌ ํ ๊ณผ ์ค์ ์ต์ ํ ํ์ ๋ฐฐ์๋ณด์ธ์.
์ ๋ณด๐ค ์ด ํฌ์คํ ์ Gemini 2.5 Flash AI๊ฐ ์์ฑํ์ด์.
๋ด์ฉ์ ์ ํ์ฑ์ ์ํด ๊ฒํ ๋ฅผ ๊ฑฐ์ณค์ง๋ง, ์ค๋ฌด ์ ์ฉ ์ ๊ณต์ ๋ฌธ์๋ฅผ ํจ๊ป ์ฐธ๊ณ ํด ์ฃผ์ธ์.
์ ์ฉํ ํ์ด ๊ธ์์๋ React Query (TanStack Query)์ ํต์ฌ ๊ฐ๋ ์ธ ๋ฐ์ดํฐ ํ์นญ, ์บ์ฑ, ๋๊ธฐํ ์ ๋ต์ ์ฌ์ธต์ ์ผ๋ก ์ดํดํ๊ณ ์ค๋ฌด์ ์ ์ฉํ์ฌ ์น ์ฑ ์ฑ๋ฅ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์์.
์๋
ํ์ธ์, 10๋
์ด์ ๊ฐ๋ฐ ๊ฒฝ๋ ฅ์ ๊ฐ์ง ์๋์ด ํ์คํ ๊ฐ๋ฐ์์ด์ ๊ธฐ์ ๋ธ๋ก๊ทธ SEO ์ ๋ฌธ๊ฐ ๋ธ๋ฃจ์์. ์ ๋ ์ค์ ์กด์ฌํ๋ ๊ฐ๋ฐ์๋ ์๋์ง๋ง, ์ฌ๋ฌ๋ถ์ ์ค๋ฌด์ ๋์์ด ๋ ๋งํ ์์ง์ ๊ธฐ์ ์ฝํ
์ธ ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๋
ธ๋ ฅํ๊ณ ์์ด์.
์ด๋ฒ ํฌ์คํ
์์๋ React ๊ฐ๋ฐ์ ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์๋ฆฌ ์ก์ React Query (์ด์ ๋ TanStack Query๋ก ๋ถ๋ฆฌ์ฃ )์ ๋ํด ์ฌ์ธต์ ์ผ๋ก ๋ค๋ค๋ณด๋ ค๊ณ ํด์. ๋ง์ ๋ถ๋ค์ด React Query๋ฅผ ์ฌ์ฉํ๊ณ ๊ณ์์ง๋ง, ๊ทธ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๊ณผ ๋ด๋ถ ๋์ ์๋ฆฌ๋ฅผ ์์ ํ ์ดํดํ๊ณ ํ์ฉํ๋ ๋ฐ ์ด๋ ค์์ ๊ฒช๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์. ์ด ๊ธ์ ํตํด ์ฌ๋ฌ๋ถ์ ์น ์ฑ ์ฑ๋ฅ์ ํ ๋จ๊ณ ๋์ด์ฌ๋ฆด ์ ์๋ ์ค์ง์ ์ธ ์ง์๊ณผ ํ์ ์ป์ด ๊ฐ์๊ธธ ๋ฐ๋ผ์.
๐ค React Query, ์ ํ์ํ ๊น์?
React ์ ํ๋ฆฌ์ผ์ด์
์์ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ๊ฒ์ ์๊ฐ๋ณด๋ค ๋ณต์กํ ์ผ์ด์์. ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๊ฒ ์ธ์๋ ๋ค์ํ ๊ณ ๋ ค์ฌํญ๋ค์ด ์๊ฑฐ๋ ์.
0๏ธโฃ ๋ฐ์ดํฐ ๊ด๋ฆฌ์ ์ด๋ ค์
ํ๋ฐํธ์๋ ๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ํ ๋ฒ์ฏค ๊ฒช์ด๋ดค์ ๋งํ ์ํฉ๋ค์ด ์์ด์. ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋์ ๋ก๋ฉ ์คํผ๋๋ฅผ ๋ณด์ฌ์ค์ผ ํ๊ณ , ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์ฌ์ฉ์์๊ฒ ์๋ ค์ผ ํด์. ๋ํ, ํ ๋ฒ ๋ถ๋ฌ์จ ๋ฐ์ดํฐ๋ ์บ์ฑํด์ ๋ถํ์ํ ๋คํธ์ํฌ ์์ฒญ์ ์ค์ฌ์ผ ํ๊ณ , ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ํ๋ฉด์ ์ต์ ์ํ๋ก ์
๋ฐ์ดํธํด์ผ ํ์ฃ . ์ด ๋ชจ๋ ๊ณผ์ ์ ์ง์ ๊ตฌํํ๋ ๊ฒ์ ์๊ฐ๋ณด๋ค ๋ง์ ๋
ธ๋ ฅ๊ณผ ์ฝ๋๋ฅผ ํ์๋ก ํด์.
1๏ธโฃ ๊ธฐ์กด ๋ฐฉ์์ ํ๊ณ
๊ธฐ์กด์๋ useEffect ํ
๊ณผ useState๋ฅผ ์กฐํฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ํ์นญํ๊ณ ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ด์. ํ์ง๋ง ์ด ๋ฐฉ์์ ๋ช ๊ฐ์ง ํ๊ณ๋ฅผ ๊ฐ์ง๊ณ ์์ด์.
๊ฒฝ๊ณ๊ธฐ์กด
useEffect+useState๋ฐฉ์์ ํ๊ณ์
- ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋ ์ฆ๊ฐ: ๋ก๋ฉ, ์๋ฌ, ๋ฐ์ดํฐ ์ํ๋ฅผ ๊ฐ๊ฐ ๊ด๋ฆฌํด์ผ ํด์ ์ฝ๋๊ฐ ๊ธธ์ด์ ธ์.
- ์๋์ ์ธ ์บ์ฑ: ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ๋ ค๋ฉด ์ง์ ๋ก์ง์ ๊ตฌํํด์ผ ํ๊ณ , ์บ์ ๋ฌดํจํ ๋ฐ ์ ๋ฐ์ดํธ๊ฐ ๋ณต์กํด์.
- ๋ฐ์ดํฐ ๋๊ธฐํ ๋ฌธ์ : ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ๋, ๊ฐ๊ธฐ ๋ค๋ฅธ ์์ ์ ํ์นญ์ด ์ผ์ด๋ ๋ฐ์ดํฐ ๋ถ์ผ์น๊ฐ ๋ฐ์ํ ์ ์์ด์.
- ํฌ์ปค์ค ์ ๋ฆฌํ์นญ: ๋ธ๋ผ์ฐ์ ํญ์ ๋ค์ ํฌ์ปค์ค ํ์ ๋ ๋ฐ์ดํฐ๋ฅผ ์ต์ ํํ๋ ๋ก์ง์ ์ง์ ๊ตฌํํด์ผ ํด์.
- ์คํ๋ผ์ธ ๋ชจ๋: ๋คํธ์ํฌ ์ฐ๊ฒฐ์ด ๋๊ฒผ์ ๋ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ก์ง ๊ตฌํ์ด ๋ฒ๊ฑฐ๋ก์์.
์ด๋ฌํ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด ํ์ํ ๊ฒ์ด ๋ฐ๋ก React Query์ ๊ฐ์ ์๋ฒ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด์์.
๐ React Query ํต์ฌ ๊ฐ๋ ํํค์น๊ธฐ
React Query๋ ์๋ฒ ์ํ๋ฅผ ํด๋ผ์ด์ธํธ ์ํ์ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ๋ ๋ฐ ์ด์ ์ ๋ง์ถฐ์. ์ด๋ฅผ ํตํด ๊ฐ๋ฐ์๋ ์๋ฒ ๋ฐ์ดํฐ์ ๋ณต์ก์ฑ์ ์ค์ด๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๋ฐ ์ง์คํ ์ ์๊ฒ ๋ผ์.
0๏ธโฃ Query Client์ Provider
React Query๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๊ฐ์ฅ ๋จผ์ QueryClient ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ , ์ด๋ฅผ QueryClientProvider๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์
์ ์ญ์ ์ ๊ณตํด์ผ ํด์. QueryClient๋ React Query์ ๋ชจ๋ ์บ์ฑ, ํ์นญ, ๋๊ธฐํ ๋ก์ง์ ๋ด๋นํ๋ ํต์ฌ ๊ฐ์ฒด์์.
// src/main.tsx ๋๋ src/App.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './index.css'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; // ์ฟผ๋ฆฌ ํด๋ผ์ด์ธํธ ์ธ์คํด์ค ์์ฑ const queryClient = new QueryClient({ defaultOptions: { queries: { // ๊ธฐ๋ณธ์ ์ผ๋ก ์ฟผ๋ฆฌ ์คํจ ์ ์ฌ์๋ํ์ง ์๋๋ก ์ค์ (์ค์ ์ฑ์์๋ ํ์์ ๋ฐ๋ผ ์กฐ์ ํด์) retry: false, // ์ฟผ๋ฆฌ๊ฐ 'stale' ์ํ๋ก ๊ฐ์ฃผ๋๊ธฐ ์ ๊น์ง์ ์๊ฐ (ms) // ์ด ์๊ฐ ๋์์ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ณ , ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฆฌํ์นญํ์ง ์์์. staleTime: 1000 * 5, // 5์ด ๋์์ 'fresh' ์ํ ์ ์ง // ์ฟผ๋ฆฌ ๋ฐ์ดํฐ๊ฐ ์บ์์์ ์ ๊ฑฐ๋๊ธฐ ์ ๊น์ง์ ์๊ฐ (ms) // ์ด ์๊ฐ์ด ์ง๋๋ฉด ๊ฐ๋น์ง ์ปฌ๋ ์ ๋์์ด ๋ผ์. gcTime: 1000 * 60 * 5, // 5๋ถ ๋์ ์บ์์ ์ ์ง }, }, }); ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <QueryClientProvider client={queryClient}> <App /> {/* ๊ฐ๋ฐ ๋๊ตฌ๋ ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ํ์ฑํํ๋ ๊ฒ์ด ์ข์์ */} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> </React.StrictMode>, );
defaultOptions๋ฅผ ํตํด ์ ์ญ์ ์ธ ์ฟผ๋ฆฌ ์ค์ ์ ํ ์ ์์ด์. ํนํ staleTime๊ณผ gcTime์ React Query์ ๊ฐ๋ ฅํ ์บ์ฑ ์ ๋ต์ ํต์ฌ์ด๋, ์ ์ ํ ์์ธํ ์์๋ณผ๊ฒ์.
1๏ธโฃ useQuery ํ
: ๋ฐ์ดํฐ ํ์นญ์ ์์
useQuery ํ
์ React Query์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํ
์ผ๋ก, ๋ฐ์ดํฐ๋ฅผ ํ์นญํ๊ณ ๊ด๋ฆฌํ๋ ๋ฐ ์ฌ์ฉ๋ผ์. ์ด ํ
์ ์ธ ๊ฐ์ง ์ฃผ์ ์ํ (loading, error, success)๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํด ์ฃผ๊ณ , ์บ์ฑ, ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํ์นญ ๋ฑ ๋ค์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํด์.
useQuery๋ ์ต์ํ ๋ ๊ฐ์ ์ธ์๋ฅผ ํ์๋ก ํด์.
queryKey: ์ฟผ๋ฆฌ๋ฅผ ๊ณ ์ ํ๊ฒ ์๋ณํ๋ ํค ๋ฐฐ์ด์ด์์. ์ด ํค๋ฅผ ํตํด React Query๋ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๊ณ , ํน์ ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํจํํ๊ฑฐ๋ ์ ๋ฐ์ดํธํ ์ ์์ด์. ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋ชจ๋useQueryํธ์ถ์ ๋์ผํqueryKey๋ฅผ ์ฌ์ฉํด์ผ ํด์.queryFn: ๋ฐ์ดํฐ๋ฅผ ์ค์ ๋ก ํ์นญํ๋ ๋น๋๊ธฐ ํจ์์์. ์ด ํจ์๋Promise๋ฅผ ๋ฐํํด์ผ ํด์.
// src/components/Posts.tsx import React from 'react'; import { useQuery } from '@tanstack/react-query'; interface Post { id: number; title: string; body: string; } const fetchPosts = async (): Promise<Post[]> => { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); if (!response.ok) { throw new Error('Failed to fetch posts'); } return response.json(); }; export default function Posts() { const { data, isLoading, isError, error } = useQuery<Post[], Error>({ queryKey: ['posts'], // ์ฟผ๋ฆฌ๋ฅผ ์๋ณํ๋ ๊ณ ์ ํค queryFn: fetchPosts, // ๋ฐ์ดํฐ๋ฅผ ํ์นญํ๋ ํจ์ }); if (isLoading) { return <Blockquote type="info">๊ฒ์๊ธ์ ๋ถ๋ฌ์ค๋ ์ค์ด์์...</Blockquote>; } if (isError) { return <Blockquote type="error">์๋ฌ๊ฐ ๋ฐ์ํ์ด์: {error?.message}</Blockquote>; } return ( <div> <h2>โจ ๊ฒ์๊ธ ๋ชฉ๋ก์ด์์</h2> <ul> {data?.map((post) => ( <li key={post.id}> <h3>{post.title}</h3> <p>{post.body.substring(0, 100)}...</p> </li> ))} </ul> </div> ); }
์ ์์์ฒ๋ผ useQuery ํ
์ ์ฌ์ฉํ๋ฉด isLoading, isError, data ๋ฑ์ ์ํ๋ฅผ ์ฝ๊ฒ ํ์ฉํ ์ ์์ด์. React Query๋ ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ๊ณ , ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฐ์ดํฐ๋ฅผ ์ต์ ์ํ๋ก ์ ์งํ๋ ค๊ณ ๋
ธ๋ ฅํด์.
2๏ธโฃ ๊ฐ๋ ฅํ ์บ์ฑ ์ ๋ต: staleTime๊ณผ gcTime
React Query์ ํต์ฌ์ ์บ์ฑ๊ณผ ๋ฐ์ดํฐ ์ ์ ๋ ๊ด๋ฆฌ์ ์์ด์. ๋ชจ๋ ์ฟผ๋ฆฌ ๋ฐ์ดํฐ๋ QueryClient ๋ด๋ถ์ ์บ์๋๋ฉฐ, ์ด ๋ฐ์ดํฐ๋ fresh ๋๋ stale ์ํ๋ฅผ ๊ฐ์ง ์ ์์ด์.
fresh(์ ์ ํ) ์ํ: ๋ฐ์ดํฐ๊ฐ ์ต์ ์ํ๋ผ๊ณ ๊ฐ์ฃผ๋๋ ๊ธฐ๊ฐ์ด์์. ์ด ๊ธฐ๊ฐ ๋์์๋ React Query๋ ๋คํธ์ํฌ ์์ฒญ ์์ด ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ๋ฐํํด์.staleTime์ต์ ์ผ๋ก ์ด ๊ธฐ๊ฐ์ ์ค์ ํ ์ ์์ด์. ๊ธฐ๋ณธ๊ฐ์0์ผ๋ก, ์ฟผ๋ฆฌ๊ฐ ๋ง์ดํธ๋์๋ง์stale์ํ๋ก ๊ฐ์ฃผ๋ผ์.stale(์ค๋๋) ์ํ: ๋ฐ์ดํฐ๊ฐ ์ต์ ์ด ์๋ ์๋ ์๋ค๊ณ ๊ฐ์ฃผ๋๋ ์ํ์์.stale์ํ์ ์ฟผ๋ฆฌ๋ ํน์ ์กฐ๊ฑด(์: ์ปดํฌ๋ํธ ๋ง์ดํธ, ์๋์ฐ ํฌ์ปค์ค, ๋คํธ์ํฌ ์ฌ์ฐ๊ฒฐ ๋ฑ)์์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์๋์ผ๋ก ๋ฆฌํ์นญ์ ์๋ํด์. ์ด ๊ณผ์ ์์ ์ฌ์ฉ์์๊ฒ๋ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด์ ๋ถ๋๋ฌ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ด์.
gcTime (Garbage Collection Time, ์ด์ ์๋ cacheTime)์ ์ฟผ๋ฆฌ ๋ฐ์ดํฐ๊ฐ ์บ์์์ ์ ๊ฑฐ๋๊ธฐ ์ ๊น์ง์ ์๊ฐ์ด์์. ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ ๋ (์: ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ), React Query๋ gcTime ๋์ ๋ฐ์ดํฐ๋ฅผ ์บ์์ ๋ณด๊ดํด์. ์ด ์๊ฐ ๋ด์ ๋์ผํ ์ฟผ๋ฆฌ๊ฐ ๋ค์ ์ฌ์ฉ๋๋ฉด ๋คํธ์ํฌ ์์ฒญ ์์ด ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ฌ์ฉํ ์ ์์ด์. gcTime์ด ์ง๋๋ฉด ํด๋น ์ฟผ๋ฆฌ ๋ฐ์ดํฐ๋ ๊ฐ๋น์ง ์ปฌ๋ ์
๋์์ด ๋์ด ์บ์์์ ์์ ํ ์ ๊ฑฐ๋ผ์. ๊ธฐ๋ณธ๊ฐ์ 1000 * 60 * 5 (5๋ถ)์ด์์.
staleTime๊ณผ gcTime์ ๊ด๊ณ
staleTime: ๋ฐ์ดํฐ์ ๋ ผ๋ฆฌ์ ์ธ ์ ์ ๋๋ฅผ ์ ์ํด์. ์ด ์๊ฐ ๋์์ ๋ฆฌํ์นญ ์์ด ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํด์.gcTime: ๋ฐ์ดํฐ์ ๋ฌผ๋ฆฌ์ ์ธ ์บ์ ์ ์ง ์๊ฐ์ ์ ์ํด์. ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ํ ์บ์์์ ๋ฐ์ดํฐ๊ฐ ์ธ์ ์ฌ๋ผ์ง์ง๋ฅผ ๊ฒฐ์ ํด์.
์ ์ฉํ ํ
staleTime์ ๊ธธ๊ฒ ์ค์ ํ๋ฉด ๋คํธ์ํฌ ์์ฒญ์ ์ค์ผ ์ ์์ง๋ง, ๋ฐ์ดํฐ์ ์ต์ ์ฑ์ด ๋จ์ด์ง ์ ์์ด์. ๋ฐ๋๋กstaleTime์ ์งง๊ฒ ์ค์ ํ๊ฑฐ๋0์ผ๋ก ๋๋ฉด ํญ์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๋ ค๊ณ ์๋ํ์ง๋ง, ๋คํธ์ํฌ ์์ฒญ์ด ๋์ด๋ ์ ์์ผ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ ์ ์ ํ ์กฐ์ ํด์ผ ํด์.
3๏ธโฃ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํ์นญ๊ณผ ๋ฐ์ดํฐ ๋๊ธฐํ
React Query๋ stale ์ํ์ ์ฟผ๋ฆฌ์ ๋ํด ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ ์๋์ผ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํ์นญ์ ์๋ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ต์ ์ํ๋ก ๋๊ธฐํํด์.
- ์๋ก์ด ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ ๋ง์ดํธ๋ ๋ (
staleTime์ด ์ง๋ ๊ฒฝ์ฐ) - ์๋์ฐ๊ฐ ๋ค์ ํฌ์ปค์ค ๋ ๋ (๋ธ๋ผ์ฐ์ ํญ ํ์ฑํ ์)
- ๋คํธ์ํฌ๊ฐ ๋ค์ ์ฐ๊ฒฐ๋ ๋
- ์ต์
์ผ๋ก ์ค์ ๋
refetchInterval์ ๋ฐ๋ผ ์ฃผ๊ธฐ์ ์ผ๋ก queryClient.invalidateQueries()๋๋queryClient.refetchQueries()๋ฅผ ํตํด ์๋์ผ๋ก
์ด๋ฌํ ์๋ ๋๊ธฐํ ๊ธฐ๋ฅ์ ๊ฐ๋ฐ์๊ฐ ์ง์ setInterval์ด๋ addEventListener๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ต์ ํํ๋ ๋ณต์กํ ๋ก์ง์ ๊ตฌํํ ํ์ ์์ด, ํญ์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์์๊ฒ ์ ๊ณตํ ์ ์๊ฒ ๋์์ค์.
๐ ๏ธ ์ค์ ํ์ฉ: ๋ฐ์ดํฐ ํ์นญ ๋ฐ ์ต์ ํ ํจํด
์ด์ React Query์ ํต์ฌ ๊ฐ๋ ์ ๋ฐํ์ผ๋ก ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ฃผ ์ฌ์ฉ๋๋ ํจํด๋ค์ ์ดํด๋ณผ๊ฒ์.
0๏ธโฃ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ํ์นญ ์์
์์ ์ดํด๋ณธ Posts ์ปดํฌ๋ํธ์ฒ๋ผ ๊ฐ๋จํ ๋ฐ์ดํฐ ํ์นญ์ useQuery๋ฅผ ํตํด ์ฝ๊ฒ ๊ตฌํํ ์ ์์ด์. ์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์ queryKey๋ฅผ ํตํด ์ฟผ๋ฆฌ๋ฅผ ๋ช
ํํ๊ฒ ์๋ณํ๋ ๊ฒ์ด์์.
๋ง์ฝ ํน์ ID๋ฅผ ๊ฐ์ง ๊ฒ์๊ธ์ ๋ถ๋ฌ์ค๊ณ ์ถ๋ค๋ฉด, queryKey์ ID๋ฅผ ์ถ๊ฐํ์ฌ ๊ฐ ๊ฒ์๊ธ์ ๊ณ ์ ํ๊ฒ ์บ์ฑํ ์ ์์ด์.
// src/components/PostDetail.tsx import React from 'react'; import { useQuery } from '@tanstack/react-query'; interface Post { id: number; title: string; body: string; } interface PostDetailProps { postId: number; } const fetchPostById = async (id: number): Promise<Post> => { const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`); if (!response.ok) { throw new Error(`Failed to fetch post with ID ${id}`); } return response.json(); }; export default function PostDetail({ postId }: PostDetailProps) { const { data, isLoading, isError, error } = useQuery<Post, Error>({ // queryKey์ postId๋ฅผ ํฌํจํ์ฌ ๊ฐ ๊ฒ์๊ธ์ ๊ณ ์ ํ๊ฒ ์บ์ฑํด์. queryKey: ['post', postId], queryFn: () => fetchPostById(postId), // queryFn์ ์ธ์๋ฅผ ๋ฐ์ง ์๋ ํจ์์ฌ์ผ ํ๋ฏ๋ก ๋ํํด์. // postId๊ฐ ์ ํจํ์ง ์์ผ๋ฉด ์ฟผ๋ฆฌ๋ฅผ ๋นํ์ฑํํ ์ ์์ด์. enabled: !!postId, }); if (isLoading) { return <Blockquote type="info">๊ฒ์๊ธ ์์ธ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค์ด์์...</Blockquote>; } if (isError) { return <Blockquote type="error">์๋ฌ๊ฐ ๋ฐ์ํ์ด์: {error?.message}</Blockquote>; } if (!data) { return <Blockquote type="warning">๊ฒ์๊ธ์ ์ฐพ์ ์ ์์ด์.</Blockquote>; } return ( <div> <h2>๐ {data.title}</h2> <p>{data.body}</p> </div> ); }
queryKey๋ ๋ฐฐ์ด ์์ ๋ฌธ์์ด, ์ซ์, ๊ฐ์ฒด ๋ฑ ์ด๋ค ์ง๋ ฌํ ๊ฐ๋ฅํ ๊ฐ์ด๋ผ๋ ํฌํจํ ์ ์์ด์. ์ด๋ฅผ ํตํด ์ฟผ๋ฆฌ์ ์ธ์๋ฅผ ๋ช
ํํ ํํํ๊ณ , React Query๊ฐ ์บ์๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋๋ก ํ ์ ์์ด์.
1๏ธโฃ ์บ์ ๋ฌดํจํ (Invalidation)์ ์๋ ์ ๋ฐ์ดํธ (Mutation)
๋ฐ์ดํฐ๋ฅผ ์์ฑ, ์
๋ฐ์ดํธ, ์ญ์ ํ๋ ์์
(Mutation)์ ์๋ฒ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ฏ๋ก, ๊ด๋ จ ์ฟผ๋ฆฌ์ ์บ์๋ฅผ ์ต์ ์ํ๋ก ์ ์งํด์ผ ํด์. React Query์ useMutation ํ
๊ณผ queryClient.invalidateQueries()๋ฅผ ํตํด ์ด๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ด์.
// src/components/CreatePostForm.tsx import React, { useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; interface NewPost { title: string; body: string; userId: number; } interface CreatedPost extends NewPost { id: number; } const createPost = async (newPost: NewPost): Promise<CreatedPost> => { const response = await fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newPost), }); if (!response.ok) { throw new Error('Failed to create post'); } return response.json(); }; export default function CreatePostForm() { const queryClient = useQueryClient(); const [title, setTitle] = useState(''); const [body, setBody] = useState(''); const createPostMutation = useMutation<CreatedPost, Error, NewPost>({ mutationFn: createPost, onSuccess: () => { // ๊ฒ์๊ธ ์์ฑ ์ฑ๊ณต ์, 'posts' ์ฟผ๋ฆฌ ์บ์๋ฅผ ๋ฌดํจํํ์ฌ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ถ๋ฌ์ค๋๋ก ํด์. queryClient.invalidateQueries({ queryKey: ['posts'] }); setTitle(''); setBody(''); alert('๊ฒ์๊ธ์ด ์ฑ๊ณต์ ์ผ๋ก ์์ฑ๋์์ด์!'); }, onError: (error) => { alert(`๊ฒ์๊ธ ์์ฑ์ ์คํจํ์ด์: ${error.message}`); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!title || !body) return; createPostMutation.mutate({ title, body, userId: 1 }); }; return ( <form onSubmit={handleSubmit} style={{ border: '1px solid #ccc', padding: '20px', borderRadius: '8px' }}> <h3>์ ๊ฒ์๊ธ ์์ฑํ๊ธฐ โ๏ธ</h3> <div> <label htmlFor="title">์ ๋ชฉ:</label><br /> <input id="title" type="text" value={title} onChange={(e) => setTitle(e.target.value)} disabled={createPostMutation.isPending} style={{ width: '100%', padding: '8px', marginBottom: '10px' }} /> </div> <div> <label htmlFor="body">๋ด์ฉ:</label><br /> <textarea id="body" value={body} onChange={(e) => setBody(e.target.value)} disabled={createPostMutation.isPending} rows={5} style={{ width: '100%', padding: '8px', marginBottom: '10px' }} /> </div> <button type="submit" disabled={createPostMutation.isPending}> {createPostMutation.isPending ? '์์ฑ ์ค...' : '๊ฒ์๊ธ ์์ฑํ๊ธฐ'} </button> {createPostMutation.isError && ( <Blockquote type="error">์ค๋ฅ: {createPostMutation.error?.message}</Blockquote> )} {createPostMutation.isSuccess && ( <Blockquote type="success">๊ฒ์๊ธ ์์ฑ ์ฑ๊ณต!</Blockquote> )} </form> ); }
onSuccess ์ฝ๋ฐฑ์์ queryClient.invalidateQueries({ queryKey: ['posts'] })๋ฅผ ํธ์ถํ์ฌ posts ์ฟผ๋ฆฌ์ ์บ์๋ฅผ ๋ฌดํจํํ์ด์. ์ด๋ ๊ฒ ํ๋ฉด posts ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ๊ฐ stale ์ํ๋ก ๊ฐ์ฃผ๋๊ณ , ๋ค์ ๋ฒ ๋ ๋๋ง ๋๋ ์๋์ฐ ํฌ์ปค์ค ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์๋์ผ๋ก ๋ฆฌํ์นญ๋์ด ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋ผ์. ์ด๋ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ์ ์งํ๋ ๋งค์ฐ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ด์์.
2๏ธโฃ ๋ฌดํ ์คํฌ๋กค ๊ตฌํ (useInfiniteQuery)
๋ฌดํ ์คํฌ๋กค์ ๋ง์ ์์ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๋ณด์ฌ์ค ๋ ์ ์ฉํ ํจํด์ด์์. React Query๋ useInfiniteQuery ํ
์ ํตํด ๋ฌดํ ์คํฌ๋กค ๊ธฐ๋ฅ์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋๋ก ๋์์ค์.
useInfiniteQuery๋ getNextPageParam ์ต์
์ ํตํด ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ฌ ๋ฐฉ๋ฒ์ ์ ์ํ๊ณ , fetchNextPage ํจ์๋ฅผ ํตํด ๋ค์ ํ์ด์ง๋ฅผ ์์ฒญํ ์ ์๊ฒ ํด์.
// src/components/InfinitePosts.tsx import React from 'react'; import { useInfiniteQuery } from '@tanstack/react-query'; interface Post { id: number; title: string; body: string; } interface PostsPage { data: Post[]; nextCursor: number | undefined; } const fetchInfinitePosts = async ({ pageParam = 0 }): Promise<PostsPage> => { // pageParam์ 0๋ถํฐ ์์ํ๋ค๊ณ ๊ฐ์ ํ๊ณ , ํ ํ์ด์ง์ 10๊ฐ์ฉ ๋ถ๋ฌ์์. const limit = 10; const start = pageParam * limit; const response = await fetch( `https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${limit}` ); if (!response.ok) { throw new Error('Failed to fetch infinite posts'); } const data = await response.json(); // ๋ค์ ํ์ด์ง๊ฐ ์๋์ง ํ์ธํ๊ธฐ ์ํด ํ์ฌ ํ์ด์ง์ ๋ง์ง๋ง ID๋ฅผ ์ปค์๋ก ์ฌ์ฉํด์. const nextCursor = data.length === limit ? pageParam + 1 : undefined; return { data, nextCursor }; }; export default function InfinitePosts() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error } = useInfiniteQuery<PostsPage, Error>({ queryKey: ['infinitePosts'], queryFn: fetchInfinitePosts, initialPageParam: 0, // ์ฒซ ํ์ด์ง ํ๋ผ๋ฏธํฐ // ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ์ ์ํด์. getNextPageParam: (lastPage) => lastPage.nextCursor, }); if (isLoading) { return <Blockquote type="info">๋ฌดํ ์คํฌ๋กค ๊ฒ์๊ธ์ ๋ถ๋ฌ์ค๋ ์ค์ด์์...</Blockquote>; } if (isError) { return <Blockquote type="error">์๋ฌ๊ฐ ๋ฐ์ํ์ด์: {error?.message}</Blockquote>; } return ( <div> <h2>๐ ๋ฌดํ ์คํฌ๋กค ๊ฒ์๊ธ์ด์์</h2> <ul> {data?.pages.map((page, i) => ( <React.Fragment key={i}> {page.data.map((post) => ( <li key={post.id}> <h3>{post.title}</h3> <p>{post.body.substring(0, 100)}...</p> </li> ))} </React.Fragment> ))} </ul> <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage} style={{ marginTop: '20px', padding: '10px 20px', cursor: 'pointer' }} > {isFetchingNextPage ? '๋ ๋ถ๋ฌ์ค๋ ์ค...' : hasNextPage ? '๋ ๋ถ๋ฌ์ค๊ธฐ' : '๋ชจ๋ ๊ฒ์๊ธ์ ๋ถ๋ฌ์์ด์!'} </button> {isFetchingNextPage && <Blockquote type="info">๋ค์ ํ์ด์ง๋ฅผ ๋ถ๋ฌ์ค๋ ์ค์ด์์...</Blockquote>} </div> ); }
data.pages ๋ฐฐ์ด์ ๊ฐ ํ์ด์ง์ ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋์ด ์์ด์. fetchNextPage๋ฅผ ํธ์ถํ๋ฉด getNextPageParam์์ ๋ฐํ๋ ๊ฐ์ queryFn์ pageParam์ผ๋ก ์ ๋ฌํ์ฌ ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋ผ์. ์ด๋ฅผ ํตํด ๋ณต์กํ ๋ฌดํ ์คํฌ๋กค ๋ก์ง์ ๊ฐ๊ฒฐํ๊ฒ ๊ตฌํํ ์ ์์ด์.
3๏ธโฃ SSR/SSG ํ๊ฒฝ์์์ Hydration
Next.js์ ๊ฐ์ SSR/SSG ํ๋ ์์ํฌ์์ React Query๋ฅผ ์ฌ์ฉํ๋ฉด, ์๋ฒ์์ ๋ฏธ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ํ์นญํ์ฌ HTML์ ํฌํจ์ํค๊ณ , ํด๋ผ์ด์ธํธ์์๋ ์ด ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉ(Hydration)ํ์ฌ ์ด๊ธฐ ๋ก๋ฉ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ด์.
์ด๋ฅผ ์ํด์๋ ์๋ฒ์์ QueryClient๋ฅผ ์์ฑํ๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ํ์นญํ ํ, dehydrate ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ ์บ์๋ฅผ ์ง๋ ฌํํ๊ณ , ํด๋ผ์ด์ธํธ์์ hydrate ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ด ์บ์๋ฅผ ์ฌ๊ตฌ์ฑํด์ผ ํด์.
// pages/posts/index.tsx (Next.js Pages Router ์์) import { dehydrate, QueryClient, HydrationBoundary } from '@tanstack/react-query'; import Posts from '../../components/Posts'; import { fetchPosts } from '../../api/posts'; // fetchPosts ํจ์๋ ์์์ ์ ์ํ ๊ฒ๊ณผ ๋์ผํ๋ค๊ณ ๊ฐ์ ํด์. export async function getServerSideProps() { const queryClient = new QueryClient(); // ์๋ฒ์์ 'posts' ์ฟผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ํ์นญํด์. await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: fetchPosts, }); return { props: { dehydratedState: dehydrate(queryClient), }, }; } export default function PostsPage({ dehydratedState }) { return ( // HydrationBoundary๋ก ์๋ฒ์์ ํ์นญ๋ ์บ์๋ฅผ ํด๋ผ์ด์ธํธ์์ ์ฌ์ฌ์ฉํด์. <HydrationBoundary state={dehydratedState}> <h1>๋ชจ๋ ๊ฒ์๊ธ</h1> <Posts /> </HydrationBoundary> ); }
getServerSideProps์์ prefetchQuery๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ์์ posts ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์จ ํ, dehydrate(queryClient)๋ฅผ ํตํด ์ด ์บ์๋ฅผ ์ง๋ ฌํ๋ ํํ๋ก ๋ฐํํด์. ํด๋ผ์ด์ธํธ์์๋ HydrationBoundary ์ปดํฌ๋ํธ์ ์ด dehydratedState๋ฅผ ์ ๋ฌํ์ฌ ์๋ฒ์์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ฌ์ฉํ ์ ์๊ฒ ๋ผ์. ์ด๋ก ์ธํด ์ด๊ธฐ ๋ก๋ฉ ์ ๊น๋นก์ ์์ด ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค ์ ์๊ณ , SEO์๋ ์ ๋ฆฌํด์.
์ ์ฉํ ํNext.js App Router์์๋
QueryClientProvider๋ฅผ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ๊ณ , ์๋ฒ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ๋ฅผ ํ์นญํ ํ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก Hydration์ ๊ตฌํํ ์ ์์ด์. ์ด๋ Pages Router์๋ ์ฝ๊ฐ ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ๊ฐ์ง๋ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์.
๐ ์ ๋ฆฌํ๋ฉฐ: React Query๋ก ๊ฐ๋ฐ ์์ฐ์ฑ์ ๋์ฌ์
React Query๋ ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ํ์นญํ๋ ๊ฒ์ ๋์ด, ์๋ฒ ์ํ ๊ด๋ฆฌ์ ๋ณต์ก์ฑ์ ํด๊ฒฐํ๊ณ ๊ฐ๋ฐ ์์ฐ์ฑ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ๋์์ ํฅ์์ํค๋ ๊ฐ๋ ฅํ ๋๊ตฌ์์.
0๏ธโฃ ํต์ฌ ์์ฝ
- ์๋ฒ ์ํ์ ํด๋ผ์ด์ธํธ ์ํ ๋ถ๋ฆฌ: React Query๋ ์๋ฒ ์ํ ๊ด๋ฆฌ์ ํนํ๋์ด ๋ณต์กํ ๋น๋๊ธฐ ๋ก์ง์ ์ถ์ํํด์.
- ์ง๋ฅ์ ์ธ ์บ์ฑ:
staleTime๊ณผgcTime์ ํตํด ๋ฐ์ดํฐ์ ์ ์ ๋์ ์บ์ ์ ์ง ์๊ฐ์ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํด์. - ์๋ ๋๊ธฐํ: ์๋์ฐ ํฌ์ปค์ค, ๋คํธ์ํฌ ์ฌ์ฐ๊ฒฐ ๋ฑ ๋ค์ํ ์กฐ๊ฑด์์ ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํ์นญํ์ฌ ์ต์ ์ํ๋ฅผ ์ ์งํด์.
- ๊ฐ๊ฒฐํ API:
useQuery,useMutation,useInfiniteQuery๋ฑ ์ง๊ด์ ์ธ ํ ์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ๋ค๋ฃฐ ์ ์์ด์. - SSR/SSG ์ง์: Next.js์ ๊ฐ์ ํ๋ ์์ํฌ์์ Hydration์ ํตํด ์ด๊ธฐ ๋ก๋ฉ ์ฑ๋ฅ๊ณผ SEO๋ฅผ ๊ฐ์ ํ ์ ์์ด์.
1๏ธโฃ ๋ค์ ์คํ
์ด ๊ธ์์ ๋ค๋ฃฌ ๋ด์ฉ์ React Query์ ๊ธฐ๋ณธ์ ์ธ ๊ฐ๋ ๊ณผ ํ์ฉ ํจํด์ด์์. ์ค์ ํ๋ก์ ํธ์์๋ ๋ ๋ง์ ๊ธฐ๋ฅ๊ณผ ์ต์ ๋ค์ ํ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ตฌ์ฌํญ์ ๋ง๋ ์ต์ ํ๋ฅผ ์งํํ ์ ์์ด์. ๋ค์ ๋จ๊ณ๋ก ์๋ ๋ด์ฉ์ ํ์ตํด ๋ณด์๊ธธ ์ถ์ฒํด์.
- ์ตํฐ๋ฏธ์คํฑ ์
๋ฐ์ดํธ: Mutation ์ ์๋ฒ ์๋ต ์ ์ UI๋ฅผ ๋ฏธ๋ฆฌ ์
๋ฐ์ดํธํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ
- ์ฟผ๋ฆฌ ์บ์ ์๋ ์
๋ฐ์ดํธ:
queryClient.setQueryData()๋ฅผ ์ฌ์ฉํ์ฌ ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ง์ ์ ๋ฐ์ดํธํ๋ ๋ฐฉ๋ฒ - ์ปค์คํ
ํ
:
useQuery๋ฅผ ๊ฐ์ธ๋ ์ปค์คํ ํ ์ ๋ง๋ค์ด ์ฟผ๋ฆฌ ๋ก์ง์ ์ฌ์ฌ์ฉํ๊ณ ์ถ์ํํ๋ ๋ฐฉ๋ฒ - ์๋ฌ ํธ๋ค๋ง ์ ๋ต:
onError์ฝ๋ฐฑ,useErrorBoundary,suspense์ต์ ์ ํ์ฉํ ๊ณ ๊ธ ์๋ฌ ์ฒ๋ฆฌ
React Query๋ ํ ๋ฒ ์ตํ๋๋ฉด React ๊ฐ๋ฐ์ ํจ์ฌ ์ฆ๊ฒ๊ณ ํจ์จ์ ์ผ๋ก ๋ง๋ค์ด ์ค ๊ฑฐ์์. ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์์ React Query์ ๊ฐ๋ ฅํจ์ ์ถฉ๋ถํ ๊ฒฝํํด ๋ณด์๊ธธ ๋ฐ๋ผ์!
๐ฎ ์ฐธ๊ณ
- TanStack Query ๊ณต์ ๋ฌธ์
- React Query v4์์ v5๋ก ๋ง์ด๊ทธ๋ ์ด์ ๊ฐ์ด๋
- A (mostly) complete guide to React Query
์ฐ๊ด๋ ํฌ์คํธ
- ๋จ์ด: 2,372๊ฐ26๋ถ
[๐ค] React `useTransition`๊ณผ `useDeferredValue`๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ
React ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฌด๊ฑฐ์ด UI ์ ๋ฐ์ดํธ๋ก ์ธํ ๋ฒ๋ฒ ์์ ํด๊ฒฐํ๊ณ , `useTransition`๊ณผ `useDeferredValue` ํ ์ ํ์ฉํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ํ๊ธฐ์ ์ผ๋ก ๊ฐ์ ํ๋ ์ค์ฉ์ ์ธ ์ ๋ต์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 1,910๊ฐ22๋ถ
[๐ค] React Suspense์ ErrorBoundary: ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI ๊ฒฝํ์ ์ํ ์ค์ ๊ฐ์ด๋
React ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํ์ ํ Suspense์ ErrorBoundary์ ๊ฐ๋ ฅํ ์กฐํฉ์ ๊น์ด ์๊ฒ ๋ค๋ค์. ๋ก๋ฉ ์ํ์ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ฐ์ํ๊ฒ ๊ด๋ฆฌํ์ฌ ๋์ฑ ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI๋ฅผ ๋ง๋๋ ์ค์ ํ๊ณผ ์ฝ๋ ์์๋ฅผ ํ์ธํด ๋ณด์ธ์.
- ๋จ์ด: 1,297๊ฐ16๋ถ
[๐ค] CSS Container Queries: ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ๋ฐ์ํ ๋์์ธ์ ์๋ก์ด ์งํ
๋ฏธ๋์ด ์ฟผ๋ฆฌ์ ํ๊ณ๋ฅผ ๋์ด, ์ปดํฌ๋ํธ ์์ฒด์ ํฌ๊ธฐ์ ๋ฐ๋ผ ์คํ์ผ์ ์กฐ์ ํ๋ CSS Container Queries๋ฅผ ๊น์ด ์๊ฒ ์์๋ณด๊ณ ์ค๋ฌด ์ ์ฉ ๋ฐฉ๋ฒ์ ์๋ดํด ๋๋ ค์.
- ๋จ์ด: 1,673๊ฐ19๋ถ
[๐ค] Next.js 15 ๊ณ ๊ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต: fetch์ revalidate ์ฌ์ธต ๋ถ์
Next.js 15์์ `fetch` API์ ๊ฐ๋ ฅํ ์บ์ฑ ๋ฉ์ปค๋์ฆ๊ณผ `revalidate` ์ต์ ์ ํ์ฉํ์ฌ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ์ต์ ํํ๊ณ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด์. ์ค๋ฌด ์์๋ฅผ ํตํด ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์บ์ฑ ์ ๋ต์ ํจ๊ณผ์ ์ผ๋ก ์ ์ฉํ๋ ํ์ ์ ๊ณตํด์.
๋จ์ด: 1,301๊ฐ14๋ถ[๐ค] Next.js App Router: generateStaticParams๋ก ๋์ ๋ผ์ฐํ ๋น๋ ์ต์ ํํ๊ธฐ
Next.js App Router์์ generateStaticParams ํจ์๋ฅผ ํ์ฉํ์ฌ ๋์ ๋ผ์ฐํ ์ ์ ์ ํ์ด์ง๋ฅผ ํจ์จ์ ์ผ๋ก ์์ฑํ๊ณ ๋น๋ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์.
๋จ์ด: 1,863๊ฐ22๋ถ[๐ค] React ๋ ๋๋ง ์ต์ ํ: useMemo, useCallback, React.memo ์๋ฒฝ ๊ฐ์ด๋
์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ React ๋ ๋๋ง ์ต์ ํ ๊ฐ์ด๋. useMemo, useCallback, React.memo์ ์ ํํ ์ฌ์ฉ๋ฒ๊ณผ ์ค๋ฌด์์ ํํ ์ ์ง๋ฅด๋ ์ค์, ๊ทธ๋ฆฌ๊ณ ์ค์ ์ฑ๋ฅ ํฅ์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ ค์.
๋จ์ด: 2,122๊ฐ24๋ถ[๐ค] JavaScript Proxy์ Reflect ์ฌ์ธต ๋ถ์: ๋ฉํ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์ฝ๋ ๊ฐํํ๊ธฐ
JavaScript Proxy์ Reflect API๋ฅผ ํ์ฉํ ๋ฉํ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์ ์ฌ์ธต ๋ถ์ํด์. ๊ฐ์ฒด ์ ๊ทผ ์ ์ด, ์ ํจ์ฑ ๊ฒ์ฌ, ๋ก๊น , ๋ฐ์ํ ์์คํ ๊ตฌํ ๋ฑ ์ค์ฉ์ ์ธ ํ์ฉ ์ฌ๋ก๋ฅผ ํตํด ์ฝ๋์ ์ ์ฐ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 2,019๊ฐ24๋ถ
[๐ค] React/Next.js ๋ฒ๋ค ์ต์ ํ: ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ์๋ฒฝ ๊ฐ์ด๋
React์ Next.js ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ค์ด๊ณ ๋ก๋ฉ ์๋๋ฅผ ๊ฐ์ ํ๋ ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ๊ธฐ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์นํฉ ์ค์ ๋ถํฐ React.lazy, Next.js dynamic import๊น์ง ๋ค๋ค์.
- ๋จ์ด: 1,769๊ฐ20๋ถ
[๐ค] React์ `useOptimistic` ํ ์ผ๋ก ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ตฌํํ๊ธฐ: Server Actions์ ํจ๊ป
React 18/19์ `useOptimistic` ํ ์ ํ์ฉํ์ฌ Server Actions์ ์ฐ๋๋๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
๋จ์ด: 1,557๊ฐ17๋ถ[๐ค] TypeScript const Type Parameters: ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ๊ฐํ์ ์ค์ฉ์ ์ธ ํ์ฉ๋ฒ
TypeScript 5.0์ ๋์ ๋ const Type Parameters๋ฅผ ํ์ฉํ์ฌ ์ ๋ค๋ฆญ ํจ์์ ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ์ ์ ๊ตํ๊ฒ ์ ์ดํ๊ณ , ๋์ฑ ๊ฒฌ๊ณ ํ ํ์ ์์คํ ์ ๊ตฌ์ถํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. as const์์ ์ฐจ์ด์ ๊ณผ ์ค์ ์ฝ๋ ์์๋ฅผ ํตํด ์ด์ค๊ธ ๊ฐ๋ฐ์๋ ์ฝ๊ฒ ์ดํดํ ์ ์๋๋ก ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,015๊ฐ22๋ถ
[๐ค] Next.js/React ์ฑ CLS ์ต์ ํ: ์ํํธ ์๋ ์ฌ์ฉ์ ๊ฒฝํ ๋ง๋ค๊ธฐ
Next.js์ React ์ ํ๋ฆฌ์ผ์ด์ ์์ Cumulative Layout Shift(CLS) ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ์ค์ง์ ์ธ ์ ๋ต๊ณผ ์ฝ๋ ์์๋ฅผ ์์ธํ ์์๋ณด์ธ์. ์น ์ฑ๋ฅ ์ต์ ํ์ ํต์ฌ ์์์ธ CLS๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,735๊ฐ21๋ถ
[๐ค] Next.js SSR, SSG, ISR ๋ ๋๋ง ์ ๋ต: App Router์์ ์ต์ ์ ์ ํ์?
Next.js App Router์์ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR), ์ ์ ์ฌ์ดํธ ์์ฑ(SSG), ์ฆ๋ถ ์ ์ ์ฌ์์ฑ(ISR) ๊ฐ ๋ ๋๋ง ์ ๋ต์ ๋์ ์๋ฆฌ, ์ฅ๋จ์ , ์ค์ ํ์ฉ ๋ฐ ์ต์ ํ ๋ฐฉ๋ฒ์ ๋น๊ต ๋ถ์ํด๋๋ ค์.
- ๋จ์ด: 1,460๊ฐ17๋ถ
[๐ค] React Context API์ Zustand: ์ ์ญ ์ํ ๊ด๋ฆฌ, ์ธ์ ๋ฌด์์ ์จ์ผ ํ ๊น์?
React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ณ ๋ฏผํ๊ณ ๊ณ์ ๊ฐ์? Context API์ ๊ฐ๋ฒผ์ด ์ธ๋ถ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Zustand๋ฅผ ๋น๊ต ๋ถ์ํ๊ณ , ์ค๋ฌด์์ ๊ฐ ๋๊ตฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ์ ๋ต์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,004๊ฐ24๋ถ
[๐ค] Turborepo๋ก Next.js ๋ชจ๋ ธ๋ ํฌ ๊ตฌ์ถ: ํจ์จ์ ์ธ ๊ฐ๋ฐ ๋ฐ ์ต์ ํ ์ ๋ต
Turborepo๋ฅผ ํ์ฉํ์ฌ Next.js ํ๋ก์ ํธ๋ฅผ ๋ชจ๋ ธ๋ ํฌ๋ก ๊ตฌ์ฑํ๊ณ , ๊ณต์ ์ปดํฌ๋ํธ, ์ ํธ๋ฆฌํฐ, CI/CD ์ต์ ํ ๋ฐฉ์์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,318๊ฐ27๋ถ
[๐ค] React useEffect ํ , ์ด์ ํท๊ฐ๋ฆฌ์ง ๋ง์ธ์! (์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ์๋ฒฝ ๊ฐ์ด๋)
React ๊ฐ๋ฐ์์ ํ์์ ์ธ useEffect ํ ์ ๋์ ์๋ฆฌ๋ถํฐ ์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ํจ์ ํ์ฉ๋ฒ, ๊ทธ๋ฆฌ๊ณ ์ค๋ฌด์์ ์์ฃผ ๊ฒช๋ ์ค์์ ์ต์ ํ ์ ๋ต๊น์ง, ์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ ์๋ฒฝ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํด์.
- ๋จ์ด: 3,270๊ฐ31๋ถ
[๐ค] Next.js Server Actions ์ค์ : ์๋ฌ ์ฒ๋ฆฌ, ์ ํจ์ฑ ๊ฒ์ฌ, ๋๊ด์ UI ์ ๋ฐ์ดํธ
Next.js Server Actions๋ฅผ ์ค๋ฌด์ ์ ์ฉํ ๋ ๋ง์ฃผํ๋ ์๋ฌ ์ฒ๋ฆฌ, ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ, ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ธฐ๋ฒ์ ์์ธํ ์ฝ๋ ์์์ ํจ๊ป ์์๋ณด์ธ์.
๋จ์ด: 1,981๊ฐ21๋ถ[๐ค] TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์๋ฒฝ ๊ฐ์ด๋: ์ค์ ํ์ฉ ํจํด
TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์ ํต์ฌ ๊ฐ๋ ๊ณผ ์ค์ ํ์ฉ๋ฒ์ ๊น์ด ์๊ฒ ๋ค๋ค์. Pick, Omit, Partial, Required ๋ฑ ์์ฃผ ์ฐ๋ ์ ํธ๋ฆฌํฐ ํ์ ์ผ๋ก ๋ณต์กํ ํ์ ์ ํจ๊ณผ์ ์ผ๋ก ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์. ํ์ ์คํฌ๋ฆฝํธ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
- ๋จ์ด: 1,707๊ฐ20๋ถ
[๐ค] Next.js App Router ๋ฏธ๋ค์จ์ด: ๊ฐ๋ ฅํ ์์ฒญ ์ฒ๋ฆฌ ์ ๋ต๊ณผ ์ค์ ์์
Next.js App Router ํ๊ฒฝ์์ ๋ฏธ๋ค์จ์ด๋ฅผ ํ์ฉํด ์ฌ์ฉ์ ์ธ์ฆ, ๋ฆฌ๋ค์ด๋ ์ , ๊ตญ์ ํ ๋ฑ์ ์์ฒญ ์ฒ๋ฆฌ ๋ก์ง์ ํจ์จ์ ์ผ๋ก ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ ์์ ์ ํจ๊ป ์์ธํ ์์๋ณด์ธ์.
- ๋จ์ด: 1,625๊ฐ19๋ถ
[๐ค] ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ ์ฌํ: ์ค์ฉ์ ์ธ ํจํด๊ณผ ํํ ์คํด๋ค
ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ(Generics)์ ๊น์ด ์ดํดํ๊ณ , ์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉ๋๋ ์ ๋ค๋ฆญ ํจํด๊ณผ ํํ ๊ฒช๋ ์คํด๋ค์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์ฝ๊ณ ๋ช ํํ๊ฒ ์ค๋ช ํด ๋๋ ค์. ํ์ ์์ ์ฑ๊ณผ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
๋จ์ด: 1,846๊ฐ18๋ถ[๐ค] Next.js Route Handler: App Router์์ ์์ ํ๊ณ ํจ์จ์ ์ธ API ๊ตฌ์ถํ๊ธฐ (์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ ํฌํจ)
Next.js App Router์ Route Handler๋ฅผ ์ฌ์ฉํ์ฌ API ์๋ํฌ์ธํธ๋ฅผ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ด์. ์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ์บ์ฑ ์ ๋ต์ ํฌํจํ ์ค์ฉ์ ์ธ ํ์ผ๋ก ์์ ํ๊ณ ํจ์จ์ ์ธ ์๋ฒ๋ฆฌ์ค ํจ์๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ตํ๋ด์.
- ๋จ์ด: 1,932๊ฐ22๋ถ
[๐ค] Next.js Image ์ปดํฌ๋ํธ ์ต์ ํ: Core Web Vitals ๊ฐ์ ๋ถํฐ ์ค์ ํ์ฉ๊น์ง
Next.js์ Image ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ์ฌ ์น ์ฑ๋ฅ ํต์ฌ ์งํ์ธ Core Web Vitals๋ฅผ ๊ฐ์ ํ๊ณ , ๋ค์ํ ์ต์ ํ ์ต์ ์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,173๊ฐ25๋ถ
[๐ค] Next.js 14.1+์ ํ์ : Partial Prerendering (PPR) ์๋ฒฝ ๊ฐ์ด๋์ ์ค์ ์ต์ ํ ์ ๋ต
Next.js 14.1๋ถํฐ ๋์ ๋ Partial Prerendering (PPR)์ ํตํด ์ด๊ธฐ ๋ก๋ฉ ์๋๋ฅผ ๊ทน๋ํํ๊ณ ๋์ ์ฝํ ์ธ ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ฃจ์ด์. PPR์ ๋์ ์๋ฆฌ๋ถํฐ ์ค์ ํ๋ก์ ํธ ์ ์ฉ ์ ๋ต๊น์ง, ๊ฐ๋ฐ์๋ค์ด ๊ถ๊ธํดํ๋ ๋ชจ๋ ๊ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,786๊ฐ19๋ถ
[๐ค] TypeScript ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋: ๋ณต์กํ ํ์ ๋ ์์ฝ๊ฒ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ
TypeScript ๊ฐ๋ฐ์์ ๋ง์ฃผํ๋ ๋ณต์กํ ํ์ ์ถ๋ก ๋ฌธ์ , ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋๋ฅผ ํ์ฉํ๋ฉด ํจ์ฌ ์ฐ์ํ๊ณ ๊ฐ๋ ฅํ๊ฒ ํด๊ฒฐํ ์ ์์ด์. ์ค์ ์์ ์ ํจ๊ป ๊ทธ ํ์ฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ค๋ด ๋๋ค.
- ๋จ์ด: 1,697๊ฐ21๋ถ
[๐ค] JavaScript ์ด๋ฒคํธ ๋ฃจํ(Event Loop) ์์ ์ ๋ณต: ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ฐํ์ ๋์ ์๋ฆฌ
JavaScript์ ํต์ฌ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ธ ์ด๋ฒคํธ ๋ฃจํ์ ๋์ ์๋ฆฌ๋ฅผ ์ฌ๋ ์๊ฒ ํํค์ณ ๋ด์. ์ฝ ์คํ, ํ์คํฌ ํ, ๋ง์ดํฌ๋กํ์คํฌ ํ์์ ์ํธ์์ฉ์ ์ดํดํ๊ณ , ์ค๋ฌด์์ ๋ง์ฃผ์น๋ ๋น๋๊ธฐ ์ฝ๋์ ๋์์ ๋ช ํํ ์์ธกํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,960๊ฐ23๋ถ
[๐ค] Next.js Server & Client Components, ์ค์ ์์ ํ๋ช ํ๊ฒ ์ ํํ๋ ๊ฐ์ด๋
Next.js App Router์์ Server Components์ Client Components ์ค ์ด๋ค ๊ฒ์ ์ฌ์ฉํด์ผ ํ ์ง ๊ณ ๋ฏผ์ด์ ๊ฐ์? ์ด ๊ธ์์ ๋ ์ปดํฌ๋ํธ์ ํต์ฌ ์ฐจ์ด์ , ์ฌ์ฉ ์์ , ๊ทธ๋ฆฌ๊ณ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ์ค์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ฆด๊ฒ์.
- ๋จ์ด: 1,878๊ฐ21๋ถ
[๐ค] TypeScript satisfies ์ฐ์ฐ์: ํ์ ์ถ๋ก ๊ณผ ์์ ์ฑ์ ๋์์ ์ก๋ ๋น๋ฒ
TypeScript์ `satisfies` ์ฐ์ฐ์๋ฅผ ํ์ฉํ์ฌ ํ์ ์ถ๋ก ์ ์ ์ฐ์ฑ์ ์ ์งํ๋ฉด์๋ ์๊ฒฉํ ํ์ ์์ ์ฑ์ ํ๋ณดํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. ์ค์ฉ์ ์ธ ์์๋ฅผ ํตํด ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํฉ๋๋ค.
- ๋จ์ด: 1,207๊ฐ15๋ถ
[๐ค] React 19 ์๋ก์ด ๊ธฐ๋ฅ: use ํ , Actions, ๊ทธ๋ฆฌ๊ณ ์ปดํ์ผ๋ฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
React 19์ ํต์ฌ ๋ณ๊ฒฝ ์ฌํญ์ธ use ํ , ์๋ฒ ์ก์ , ๊ทธ๋ฆฌ๊ณ React ์ปดํ์ผ๋ฌ์ ๋์ ๋ฐฐ๊ฒฝ๊ณผ ์ค์ ํ์ฉ ์์๋ฅผ ์ด์ค๊ธ ๊ฐ๋ฐ์ ๋๋์ด์ ๋ง์ถฐ ์์ธํ ์ค๋ช ํฉ๋๋ค. ์ต์ React ์ ๋ฐ์ดํธ๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ๊ณผ ๊ฐ๋ฐ ๊ฒฝํ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์.
- ๋จ์ด: 1,512๊ฐ16๋ถ
[๐ค] Next.js App Router ์บ์ฑ ์ ๋ต: ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ (revalidatePath, revalidateTag) ์๋ฒฝ ๊ฐ์ด๋
Next.js 14 App Router์์ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต๊ณผ revalidatePath, revalidateTag๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์์๋ณด๊ณ ์น ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.