yarn add react-query
yarn add json-server
yarn json-server --watch db.json --port 3001
//db.json 파일을 db로 사용하고 3001번 포트에서 서버를 시작하겠다
yarn add axios
: 데이터를 읽어오는 기능(QueryClient)를 애플리케이션 전체에 주입하는 API
import React from "react";
import Router from "./shared/router";
import { QueryClient, QueryClientProvider } from "react-query";
const App = () => {
<QueryClientProvider client={QueryClient}>
return <Router />;
</QueryClientProvider>;
};
export default App;
import { useQuery } from 'react-query';
import { fetchTodoList } from '../api/fetchTodoList';
function App() {
const info = useQuery('todos', fetchTodoList);
//const { isLoading, isError, data } = useQuery("todos", fetchTodoList);
...
}
useQuery('todos', fetchTodoList)
Query Keys
: refetching / caching 처리 / 애플리케이션 전체 맥락에서 이 쿼리를 공유하는 방법으로 사용된다. (여러 컴포넌트에 존재해도 같은 key면 같은 쿼리 및 데이터를 보장함)
Query Keys
는 한 단어일수도, 배열일 수도 nested객체 일 수도 있다.
const query1 = useQuery('qk', api); // unique
const query2 = useQuery('qk2', api); // not unique
const query3 = useQuery('qk2', api); // not unique
: key 이므로 고유한 값을 가져야 한다.
Query Keys
useQuery('todos', ...)
위와 같은 코드가 있다면 내부적으로는 아래처럼 해석된다.
queryKey === ['todos'] // 배열 형태로 갖고 있다
Query Keys
// ID가 5인 todo 아이템 1개
useQuery(['todo', 5], ...)
// queryKey === ['todo', 5]
// ID가 5인 todo 아이템 1개인데, preview 속성은 true야
useQuery(['todo', 5, { preview: true }], ...)
// queryKey === ['todo', 5, { preview: true }]
// todolist 전체인데, type은 done이야
useQuery(['todos', { type: 'done' }], ...)
// queryKey === ['todos', { type: 'done' }]
Query Functions
- resolve : 정상적으로 통신되었음을 의미
- 오류가 발생한 경우에는 그에 맞는 적절한 오류 처리 관련 로직을 삽입해 처리해줘야한다. (axios, fetch, graphgl...)
: useQuery를 통해 얻은 결과물은 객체이다.
: 객체 안에는 '조회'를 요청한 결과에 대한 거의 모든 정보가 들어있고, 그 과정에 대한 정보도 들어있다.
- 조회를 시작하면
isLoading
이true
가 되고- 조회 결과 오류가 나면
isError
가true
가,isLoading
은false
가 된다.error 객체
를 통해 더 상세한 오류 내용을 확인 할 수 있다.- 조회 결과 정상이 되면
isSuccess
가true
가 ,isLoading
은false
가 된다.data 객체
를 통해 좀 더 상세한 조회 결과를 확인할 수 있다.
: query와 다르게 mutation은 CUD에서 사용됨
// [출처] : 공식문서
function App() {
const mutation = useMutation(newTodo => {
return axios.post('/todos', newTodo)
})
return (
<div>
{mutation.isLoading ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: 'Do Laundry' })
}}
>
Create Todo
</button>
</>
)}
</div>
)
}
mutation.mutate(인자)
: 인자는 반드시 한 개의 변수 또는 객체여야 한다.
: 결과는 객체의 형태로 되어있고
: 그 객체는 항상 (isIdle | isLoading | isError| isSuccess) 중 하나의 상태이다.
: 서버 상태 관리를 쉽게 할 수 있게 도와주는 라이브러리 이다.
서버 상태 관리: 서버에 요청하고 응답받는 모든 과정과 연관된 데이터들
fetching
: 서버에서 데이터 받아오기caching
: 서버에서 받아온 데이터를 따로 보관해서 동일한 데이터가 단 시간 내에 다시 필요할 시 서버 요청없이 보관된 데이터에서 꺼내쓰기synchronizing
: 서버 상의 데이터와 보관 중인 캐시 데이터(서버 상태)를 동일하게 만들기(동기화)updating
: 서버 데이터 변경 용이 (mutation & invalidateQueries)
: 서버와의 API통신과 비동기 데이터 관리를 위해 Redux-thunk, Redux-Saga등의 미들웨어를 사용할 수 있다. 하지만 문제가 있는데,,
// React Query 미사용 시
const [todos, setTodos] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const getTodos = async () => {
setIsLoading(true);
const data = await axios.get(`${API_URL}/todos`).then(res => res.data);
setTodos(data);
setIsLoading(false);
}
useEffect(() => {
getTodos();
}, []);
// React Query 사용 시
const getTodos = () => axios.get(`${API_URL}/todos`).then(res => res.data);
const { data: todos, isLoading } = useQuery(["todos"], getTodos);
+읽어볼 만한 글) 카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유
Query
: 어떤 데이터에 대한 요청
: axios의 get요청과 유사
Mutation
: 데이터 그룹 자체를 변경하는 것 (Create, Update, Delete)
: axios의 post, put, patch, delete 요청과 유사
Query Invalidation
: 쿼리 무효화
: 기존에 가져온 Query는 서버 데이터이기 때문에 언제든지 변경이 있을 수 있다. 최신 상태가 아닐 수 있으므로 기존의 쿼리를 무효화한 후 최신화 해야 하는데 React Query는 이런 과정을 알아서 해준다.
stale-while-revalidate : 신규 데이터가 도착하는 동안 일단 기존 캐싱된 데이터를 사용하도록 하는 전략
서버의 헤더 응답 설정 Cache-control에서 아이디어 기원
⬆️ 클라이언트가 0~1초 사이에 다시 데이터를 요청하면, 서버 호출없이 캐시 데이터를 바로 사용
⬆️ 클라이언트가 1 ~ 60s 사이에 다시 데이터 요청하면, 일단 캐시 데이터를 사용하고 서버에서 신규데이터를 주면 그것으로 교체
//App.jsx
const queryClient = new QueryClient();
const App = () => {
<QueryClientProvider client = {queryClient}>
<Router/>
</QueryClientProvider>
};
}
useQuery(["todos"], getTodos, {enable: true})
: boolean 타입 (true / false)
: true일 경우에만 queryFn 실행
: default 값은 true, useQuery 자동 실행됨
const {data, refetch} = useQuery(["todos"], getTodos, {enabled: false});
return (
<div>
<button onClick = {() => refetch()}>
데이터 불러오기
</button>
</div>
);
//Get user
const {data: user} = useQuery({queryKey: ['user', email], queryFn: getUserByEmail});
const userId = user?.id;
//Get user's projects
const { status, fetchStatus, data: projects }
= useQuery({ queryKey: ['projects: userId'], queryFn: getProjectsByUser, enabled: !!userId});
// userId가 존재하기 전까지 이 쿼리는 실행되지 않음
// !!userId === Boolean(userId)
queryFn에 의해 리턴된 값을 변형시킨 후에 useQuery의 리턴 data로 넘겨줌
(단, cache data는 queryFn에서 리턴 받은 값 그대로 이다.)
import {useQuery} from "react-query"
function User(){
const { data } = useQuery( ['user'], fetchUser, { select: (user) => user.username, });
return <div>Username: {data}</div>;
}
"오래된 것 먼저, 리렌더링 되면서 새 것으로 교체"
: React Query v4 부터 라이브러리 이름이 Tanstack Query로 변경되었다. React 뿐 아니라 Vue 같은 다른 SPA 프레임워크에도 적용!!
// "react-query": "^3.39.3"
yarn add react-query
// "@tanstack/react-query": "^4.29.19"
yarn add @tanstack/react-query
+ v4부터는 query key를 반드시 배열 형태로 써야 함
기본설정 | 의미 |
---|---|
staleTime: 0 | useQuery 또는 useInfiniteQuery에 등록된 QueryFn을 통해 fetch해 온 데이터는 항상 stale data 취급 |
refetchOnMount: true | useQuery 또는 useInfiniteQuery가 있는 컴포넌트가 마운트 시 stale data를 refetch 자동 실행 |
refetchOnWindowFocus: true | 실행중인 브라우저 화면은 focus할 때마다 stale data를 refetch 자동 실행 |
refetchOnReconnect : true | Network가 끊겼다가 재연결되었을 때 stale data를 refetch 자동 실행 |
cacheTime: 5분 (1000 60 5ms) | useQuery 또는 useInfiniteQuery가 있는 컴포넌트가 언마운트 되었ㅇ르 때 inactive query라고 부르며, inactive 상태가 5분 경과 후 GC(Garbage Collector)에 의해 cache data 삭제 처리 |
retry: 3 | useQuery 또는 useInfiniteQuery에 등록된 queryFn이 API 서버에 요청을 보내서 실패하더라도 바로 에러를 띄우지 않고 총 3번까지 재요청을 자동으로 사용 |
- staleTime: 얼마의 시간이 흐른 뒤에 stale취급할 것인가 (default: 0)
staleTime > 0이면, fresh data
staleTime = 0이면, stale data- cacheTime: inactive된 이후로 메모리에 얼마만틈 있을 것인가 (default: 5분, cacheTime이 0이 되면 삭제처리됨)
isLoading
: 새로운 캐시 데이터를 서버에서 받고 있는가isFetching
: 서버에서 데이터를 받고 있는가
: 캐시 데이터가 있는 경우 isLoading
은 false
, isFetching
은 true
이다.