
참고 : https://github.com/typicode/json-server
상태관리 구상 참고 : https://velog.io/@hyeon9782/%EC%9A%B0%EC%95%84%EC%BD%98-2023-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EC%8B%A4%EC%A0%84-%ED%8E%B8-with-React-Query-Zustand
Based on the example db.json, you'll get the following routes:
GET /posts
GET /posts/:id
POST /posts
PUT /posts/:id
PATCH /posts/:id
DELETE /posts/:id
# Same for comments
GET /profile
PUT /profile
PATCH /profile
lt → <lte → <=gt → >gte → >=ne → !=GET /posts?views_gt=9000
db.json 파일에 loginMgr, chat 등 데이터베이스 구성npm run server로 JSON Server 실행 getMgrDetail, getLoginMgr로 JSON Server에서 데이터 조회saveLoginMgr, updateLoginStatus, updateChatMgr로 PATCH 요청 처리chatSeq)와 JSON Server 데이터가 동기화되도록 처리RightPanelHeader, MyCounsel에서 zustand 상태와 API 호출 연결saveLoginMgr 호출기존은 데이터 패칭을 위해서 여러 useState훅을 사용했었음
before :
const [isLoading, setLoading] = useState(false)
const [isError, setError] = useState(false)
const [data, setData] = useState({});
after :
const { status, data, error, isFetching } = useQuery(() => fetch(URL));
react query를 통해 한번에 제어가 가능해짐
| 영역 | 기존 방식(zustand) | 변경 예정(React Query) |
|---|---|---|
| 로그인 정보 조회 | zustand + getLoginMgr | useQuery로 loginInfo fetch |
| 상담 데이터(fetch) | zustand + getChatList | useQuery로 chatList fetch |
| 상담 상세(fetch) | zustand + getChatDetail | useQuery로 chatDetail fetch |
| 로그인 상태 변경 | zustand + updateLoginStatus | useMutation으로 updateLoginStatus |
| 상담 상태 변경 | zustand + updateChatStatus | useMutation으로 updateChatStatus |
| 담당자 변경 | zustand + updateChatMgr | useMutation으로 updateChatMgr |
chatSeqloginInfo)는 제거 가능import { createQueryKeys } from '@lukemorales/query-key-factory';
import type { Chat, User, Mgr } from '@/types';
export const userKeys = createQueryKeys('user', {
all: { queryKey: ['user'] },
list: null,
detail: (userId:User['userId']) => [userId],
});
export const chatKeys = createQueryKeys('chat', {
all: { queryKey: ['chat'] },
list: (mgrId: Mgr['mgrId']) => [mgrId],
detail: (chatSeq: Chat['chatSeq']) => [chatSeq],
});
export const loginKeys = createQueryKeys('login', {
all: { queryKey: ['login'] },
byId: (id: number) => ({ queryKey: ['login', id] }),
});
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { loginKeys } from '@query/queryKeys';
import { getLoginMgr, saveLoginMgr, updateLoginStatus } from '@api/loginApi';
import type { Login, Mgr } from '@/types';
export const useLogin = () => {
const loginInfoQuery = useQuery<Login>({
queryKey: loginKeys.all.queryKey,
queryFn: getLoginMgr,
/* 바로 비동기 호출을 넣을수도 있는 자리임.
queryFn: async () => { const response = await Api.get<Login[]>('/loginMgr'); return response.data[0]; }, */ });
return {
loginInfo: loginInfoQuery.data,
isLoading: loginInfoQuery.isLoading,
error: loginInfoQuery.error,
};
};
export const useSaveLoginMgrMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (mgrId: Mgr['mgrId']) => saveLoginMgr(mgrId),
onSuccess: () => {
//성공시, login관련 쿼리들 무효화 -> 재요청 발생(사용하는 컴포넌트 데이터 동기화 되는 부분)
queryClient.invalidateQueries({ queryKey: loginKeys.all.queryKey });
},
});
};
export const useUpdateLoginStatusMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ loginInfo, status }: { loginInfo: Login; status: Login['status'] }) =>
updateLoginStatus(loginInfo, status),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: loginKeys.all.queryKey });
},
});
};
import { useLogin } from '@hooks/useLogin'; //hook import
import { useChat } from '@hooks/useChat';
function MyCounsel() {
...
/*서버 영역 : react-query 관리*/
const { loginInfo, isLoading } = useLogin(); //서버에서 가져온 login정보 사용 가능
...
import { createQueryKeys } from '@lukemorales/query-key-factory';
export const chatKeys = createQueryKeys('chat', {
list: (mgrId: number) => [mgrId], // ['chat', mgrId]
detail: (chatSeq: number) => [chatSeq], // ['chat', chatSeq]
});
export const loginKeys = createQueryKeys('login', {
all: null, // ['login']
});
export const chatKeys = {
all: ['chat'] as const,
list: (mgrId: number) => ['chat', 'list', mgrId] as const,
detail: (chatSeq: number) => ['chat', 'detail', chatSeq] as const,
};
export const loginKeys = {
all: ['login'] as const,
};
사용하지 않으면 배열을 직접 작성해야하고, 네임스페이스도 직접 관리 필요
사용하게 될 경우, 오타 방지 및 네임스페이스 자동화 가능하며 추후 유지보수도 쉬움
useQuery 훅은 데이터를 불러오는 코드를 React Query 라이브러리에 등록하기 위해 사용
useQuery는 키와 데이터를 불러오는 비동기 함수를 인자로 받아 어플리케이션의 현재 상태를 나타내는 다양한 값을 반환
useMutation 훅은 원격 데이터의 생성 / 업데이트 / 삭제에 사용
useMutation 훅은 데이터를 업데이트하기 위한 비동기 함수를 인자로 갖고 뮤테이션을 실행하기 위한 뮤테이트 함수를 반환
❓ 캐싱된다는 건?
react-query는 서버에서 받아온 데이터를 기억해두고,
같은 쿼리가 또 호출되면 서버에 다시 요청하지 않고 캐시에서 꺼내줌.
→ 즉, axios.get() 결과를 저장해두는 것
❓ 무효화(invalidate)란?
"이 캐시 오래됐을 수 있어. 다시 가져와!" 라는 명령.
react-query가 그 key에 해당하는 쿼리를 자동으로 다시 fetch
'로그인' 관련 데이터가 바뀌었으니 서버에 가서 다시 받아와!
queryClient.invalidateQueries({ queryKey: ['login'] });