TIL/[TIL]

[TIL]React Query, json-server, Zustand persist, CRUD 테스트

namerong 2026. 4. 24. 12:02

1. 오늘 학습 주제

Zustand 기초 자체보다, 실제 데이터를 다루는 흐름에 더 가까운 내용을 학습했다.

특히 json-server로 가짜 API 서버를 만들고, React Query로 서버 상태를 관리하며, Zustand의 persist 미들웨어로 로컬 스토리지에 상태를 저장하는 흐름을 익혔다.

  • db.json으로 가짜 API 데이터 만들기
  • json-server를 통해 CRUD 테스트용 서버 구성하기
  • PostApi로 API 요청 로직 분리하기
  • React Query의 QueryClientProvider와 useQuery 사용법
  • Zustand persist 미들웨어로 상태를 새로고침 후에도 유지하기
  • CRUD 테스트 페이지에서 게시글 조회와 등록 흐름 이해하기

2. json-server와 db.json

실제 백엔드 없이도 API 요청을 연습하기 위해 json-server를 사용하는 구조를 학습했다.

db.json 파일은 가짜 데이터베이스 역할을 하며, JSON 구조를 기반으로 REST API처럼 동작한다.

{
  "posts": [
    {
      "id": "1",
      "title": "리액트 공부하기",
      "author": "파안다"
    },
    {
      "id": "2",
      "title": "json-server 연결하기",
      "author": "판다"
    }
  ]
}
  • db.json은 가짜 데이터 저장소 역할을 한다.
  • posts 같은 키가 하나의 리소스가 된다.
  • 프론트엔드에서 실제 API처럼 요청을 보내며 CRUD를 연습할 수 있다.

3. API 요청 로직 분리

API 통신 코드는 컴포넌트 안에 직접 길게 쓰기보다 별도 모듈로 분리하는 것이 좋다.

PostApi.js에서 게시글 관련 요청을 모아 관리했다.

const BASE_URL = "<http://localhost:3001/posts>";
export const postApi = {
  getPosts: async () => {
    const res = await fetch(BASE_URL);
    return res.json();
  },

  createPost: async (newPost) => {
    const res = await fetch(BASE_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(newPost),
    });
    return res.json();
  }
}
  • API 요청 로직을 별도 파일로 분리하면 재사용성이 좋아진다.
  • 컴포넌트는 화면과 이벤트 처리에 더 집중할 수 있다.
  • getPosts, createPost처럼 기능별로 함수를 나누면 관리가 편하다.

4. 게시글 조회와 등록

PostApi에서는 두 가지 핵심 작업을 다뤘다.

4.1 조회

const res = await fetch(BASE_URL);
return res.json();
  • GET 요청으로 전체 게시글 목록을 가져온다.
  • 응답 데이터를 JSON으로 변환해 사용한다.

4.2 등록

const res = await fetch(BASE_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(newPost),
});
  • POST 메서드로 새 글 데이터를 보낸다.
  • Content-Type을 application/json으로 지정해야 한다.
  • body에는 문자열 형태의 JSON 데이터를 담아야 한다.

정리

  • 조회는 보통 GET
  • 등록은 보통 POST
  • 요청 본문이 있을 때는 headers와 body 설정이 중요하다.

5. CRUD 테스트 페이지

crud-test/page.js에서는 가장 기본적인 CRUD 흐름 중 Read와 Create를 직접 테스트했다.

const [posts, setPosts] = useState([]);

먼저 목록을 불러오는 함수를 만들고,

const loadPosts = async () => {
  const data = await postApi.getPosts();
  setPosts(data);
};

마운트 시 한 번 실행했다.

useEffect(() => { loadPosts(); }, []);

그리고 새 글 등록 후 다시 목록을 불러와 화면을 갱신했다.

const handleAdd = async () => {
  await postApi.createPost({ title: "새로운 글" + Date.now(), author: "익명" });
  loadPosts();
};
  • 목록 조회 후 state에 저장해 화면에 출력할 수 있다.
  • 새 글 등록 후 다시 목록을 조회하면 최신 상태를 반영할 수 있다.
  • 가장 기본적인 CRUD 흐름은 조회와 등록만으로도 충분히 연습 가능하다.

6. React Query란

React Query는 서버에서 가져온 데이터를 더 효율적으로 관리하기 위한 라이브러리이다.

기존에는 useEffect와 useState를 직접 조합해 데이터를 가져왔지만, React Query를 사용하면 로딩, 에러, 캐싱 같은 기능을 더 체계적으로 다룰 수 있다.

  • React Query는 서버 상태 관리에 특화된 도구이다.
  • 비동기 데이터 요청을 더 선언적으로 처리할 수 있다.
  • 로딩, 에러, 캐시 관리를 쉽게 할 수 있다.

7. QueryClientProvider와 Provider

React Query를 사용하려면 먼저 앱 최상단에서 QueryClientProvider로 감싸야 한다.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const [queryClient] = useState(() => new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,
    },
  },
}));
<QueryClientProvider client={queryClient}>
  {children}
</QueryClientProvider>
  • React Query는 QueryClientProvider가 필요하다.
  • QueryClient는 캐시와 요청 설정을 관리한다.
  • staleTime을 설정하면 일정 시간 동안 다시 요청하지 않고 캐시를 재사용할 수 있다.

8. staleTime 개념

예제에서는 staleTime: 60 * 1000을 설정했다.

staleTime: 60 * 1000

이 설정은 1분 동안 데이터를 신선한 상태로 간주하겠다는 뜻이다.

  • staleTime 동안은 데이터를 다시 가져오지 않고 캐시를 우선 사용한다.
  • 너무 자주 같은 데이터를 요청하는 것을 줄일 수 있다.
  • 서버 데이터의 변경 빈도에 따라 적절히 조절할 수 있다.

9. useQuery 사용법

query/page.js에서는 useQuery를 사용해 유저 목록을 불러왔다.

const { data, isLoading, isError, error } = useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
});

여기서 중요한 두 가지는 다음과 같다.

  • queryKey: 캐시를 구분하는 이름
  • queryFn: 실제 데이터를 가져오는 비동기 함수

정리

  • useQuery는 조회용 데이터 요청에 자주 사용한다.
  • queryKey는 캐싱의 기준이 된다.
  • 같은 키를 사용하면 React Query가 캐시를 활용할 수 있다.

10. React Query의 상태 처리

useQuery는 로딩과 에러 상태도 함께 제공한다.

if (isLoading) return <h1>데이터를 불러오는 중입니다...</h1>;
if (isError) return <p>{error.message}</p>;

성공했을 때만 실제 데이터를 출력한다.

<ul>
  {data?.map(user => (
    <li key={user.id}>{user.name}</li>
  ))}
</ul>
  • React Query는 로딩 상태를 별도로 제공한다.
  • 에러 처리도 구조적으로 가능하다.
  • 데이터 요청 흐름이 더 명확해진다.

11. Zustand persist 미들웨어

Zustand에서 상태를 브라우저 저장소에 유지하는 방법도 학습했다.

persist 미들웨어를 사용하면 새로고침 후에도 상태가 남아 있게 만들 수 있다.

import { persist } from "zustand/middleware";
export const usePersistStroe = create(
  persist(
    (set) => ({
      theme: "light",
      toggleTheme: () => set((state) => ({
        theme: state.theme === "light" ? "dark" : "light"
      }))
    }),
    {
      name: "theme-storage"
    }
  )
);
  • persist는 Zustand 상태를 로컬 스토리지 등에 저장하는 기능이다.
  • 새로고침해도 이전 상태를 복원할 수 있다.
  • 테마, 장바구니, 로그인 유지 같은 기능에 유용하다.

12. persist의 동작 방식

예제에서는 theme 상태를 저장하고 토글했다.

theme: "light"
toggleTheme: () => set((state) => ({
  theme: state.theme === "light" ? "dark" : "light"
}))

그리고 name: "theme-storage"를 통해 저장 키 이름을 지정했다.

  • 저장소에 어떤 이름으로 저장할지 직접 정할 수 있다.
  • 상태를 바꾸면 브라우저 저장소에도 함께 반영된다.
  • 다음에 앱을 열어도 이전 값이 유지된다.

13. 오늘 학습 흐름 정리

학습은 크게 두 축으로 나눌 수 있었다.

13.1 서버 상태 관리

  • db.json으로 데이터 준비
  • json-server로 가짜 API 구성
  • PostApi로 요청 함수 분리
  • crud-test/page.js로 조회/등록 테스트
  • useQuery와 Provider로 React Query 적용

13.2 클라이언트 상태 유지

  • Zustand persist 미들웨어 사용
  • 브라우저 저장소에 상태 저장
  • 새로고침 이후에도 상태 유지

14. 오늘 배운 핵심 정리

  1. db.json은 json-server에서 가짜 데이터베이스 역할을 한다.
  2. PostApi처럼 API 요청 로직을 별도 파일로 분리하면 재사용성과 가독성이 좋아진다.
  3. 게시글 조회는 GET, 등록은 POST 방식으로 처리할 수 있다.
  4. CRUD 테스트 페이지에서는 목록 조회 후 state에 저장하고, 등록 후 다시 조회하는 흐름을 연습했다.
  5. React Query를 사용하려면 QueryClientProvider로 앱을 감싸야 한다.
  6. QueryClient는 캐시와 기본 요청 설정을 담당한다.
  7. useQuery는 조회용 서버 데이터를 다룰 때 매우 유용하다.
  8. queryKey는 캐시 구분용 키이고, queryFn은 실제 요청 함수이다.
  9. staleTime을 설정하면 일정 시간 동안 캐시 데이터를 재사용할 수 있다.
  10. Zustand의 persist 미들웨어를 사용하면 상태를 브라우저 저장소에 유지할 수 있다.
  11. 테마 같은 설정값은 persist와 매우 잘 어울리는 대표적인 상태이다.