청크&사가(기존) -> 리액트 쿼리(요새 많이 쓰는듯)
리액트 쿼리는 모듈 건드리지 않고 함. 보일러 플레이트 만들다가 오류날 일이 없음
리액트 쿼리의 사용방법이 thunk 대비 쉽고 직관적임
키워드
- Query: 어떤 데이터에 대한 요청
- Mutation: 어떤 데이터를 변경
- Query Invalidation: Query를 invalidation. 즉, 무효화 시킨다
기존에 가져온 Query는 서버 데이터이기 때문에, 언제든지 변경이 있을 수 있어요.
그렇기 때문에 최신 상태가 아닐 수 있습니다.
그런 경우, 기존의 쿼리를 무효화 시킨 후 최신화 시켜야 하겠죠.
이런 과정을 React Query에서는 알아서 해준답니다
준비
- yarn add axios
- yarn add json-server
- db.json파일 만들기(json-server npm사이트에서 샘플 가져오기)
- yarn json-server --watch db.json --port 4000
예제 파일 https://github.com/wonee09/redux-basic-todo
설치
yarn add react-query
사용법
App.jsx(프로젝트의 상위 컴포넌트)에서 리액트 쿼리 관련 설정을 해줘야 함
(App.jsx)
import React from 'react';
import Router from './shared/Router';
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
const App = () => {
return (
// 위에서 만든 클라이언트를 주입하면 프로젝트 전체에서 queryClient사용
<QueryClientProvider client={queryClient}>
<Router />
</QueryClientProvider>
);
};
export default App;
(src/api/todos.js)
서버에 요칭하는 axios관련 로직들 모아놓는 파일
// axios 요청이 들어가는 모든 모듈
import axios from 'axios';
// 조회
// TodoList.jsx에서 사용할거
const getTodos = async () => {
const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
console.log(response);
// 리턴 반드시 해줘야 함
// 필요한건 data니까 data부분만 return 해주기
return response.data;
};
// 뮤테이션(변형) - 그 중 입력 해볼거(post요청)
// 추가
// Input.jsx에서 사용할거
const addTodo = async (newTodo) => {
await axios.post(`${process.env.REACT_APP_SERVER_URL}/todos`, newTodo);
};
export { getTodos, addTodo };
(src/redux/components/TodoList/TodoList.jsx)
읽어오는 코드부분도 리액트 쿼리 이용해서 가져오기.
import React from 'react';
import { StyledDiv, StyledTodoListHeader, StyledTodoListBox } from './styles';
import Todo from '../Todo';
// ⭐ - 리액트 쿼리
import { useQuery } from 'react-query';
import { getTodos } from '../../../api/todos';
/**
* 컴포넌트 개요 : 메인 > TODOLIST. 할 일의 목록을 가지고 있는 컴포넌트
* 2022.12.16 : 최초 작성
*
* @returns TodoList 컴포넌트
*/
function TodoList({ isActive }) {
// const todos = useSelector((state) => state.todos);
// ⭐데이터 조회하는 로직 - 리액트 쿼리 방법
// 인자 1. 쿼리의 이름 2. 임포트 조회를 해오는 비동기 함수
// 결과 값을 왼쪽에서 받음
const { isLoading, isError, data } = useQuery('todos', getTodos);
if (isLoading) {
return <h1>로딩중입니다....!</h1>;
}
if (isError) {
return <h1>오류가 발생하였습니다..!</h1>;
}
return (
<StyledDiv>
<StyledTodoListHeader>{isActive ? '해야 할 일 ⛱' : '완료한 일 ✅'}</StyledTodoListHeader>
<StyledTodoListBox>
⭐ - 리액트 쿼리
{data
.filter((item) => item.isDone === !isActive)
.map((item) => {
return <Todo key={item.id} todo={item} isActive={isActive} />;
})}
{/* {todos
.filter((item) => item.isDone === !isActive)
.map((item) => {
return <Todo key={item.id} todo={item} isActive={isActive} />;
})} */}
</StyledTodoListBox>
</StyledDiv>
);
}
export default TodoList;
(src/redux/components/Input/Input.jsx)
mutation의 onSuccess(성공)이 일어났을 때 변경이 필요하면 해당 쿼리키를 invalidate해주는 부분이 필요.
✔️ invalidate의 과정
Input.jsx에서 값 입력으로 인해 서버 데이터가 변경됨
→ onSuccess가 일어나면 기존의 Query인 “todos”는 무효화
→ 새로운 데이터를 가져와서 “todos”를 최신화시킴
→ TodoList.jsx를 갱신함
따라서 계속해서 리액트 앱은 최신 상태의 서버 데이터를 유지할 수 있게 됨
...
import { addTodo } from "../../../api/todos";
import { QueryClient, useMutation } from "react-query";
...
function Input() {
// ⭐리액트 쿼리 관련 코드
// ⭐상위 컴포넌트에서 만든 newQueryClient를 이용해서 하나의 흐름으로써 QueryClient를 이용할 수 있음
const queryClient = useQueryClient();
// ⭐인자 1. 우리가 만들어놓은 api
// ⭐인자 2. 객체. 성공 실패했을때의 키 벨류형태로
const mutation = useMutation(addTodo, {
onSuccess: () => {
// db변경 일어나면 다시 뿌려줄거
// TodoList.jsx Line20에 useQuery에 첫번째 인자로 쓴 키 "todos"
// 키 "todos"를 무효화시키고 다시 불러옴
// Invalidate and refresh
// 이렇게 하면, todos라는 이름으로 만들었던 query를
// invalidate 할 수 있어요.
queryClient.invalidateQueries("todos");
},
});
...
// form 태그 내부에서의 submit이 실행된 경우 호출되는 함수
const handleSubmitButtonClick = (event) => {
...
// 추가하려는 todo를 newTodo라는 객체로 세로 만듦
const newTodo = {
title,
contents,
isDone: false,
id: uuidv4(),
};
// ⭐리액트 쿼리
// api대한 인자
mutation.mutate(newTodo);
// state 두 개를 초기화
setTitle('');
setContents('');
};
useQuery()
useQuery의 인자
- 첫 번째 인자 ‘todos’. 쿼리의 키(Query Keys)라고 부름
- refetching 하는 데에 쓰여요.
- 캐싱(caching) 처리를 하는 데에도 쓰이죠.
- 애플리케이션 전체 맥락에서 이 쿼리를 공유하는 방법으로 쓰여요. —> 그 어느 컴포넌트 곳곳에 촥촥 뿌려져 있어도 같은 key면 같은 쿼리 및 데이터 보장!!
- Query keys는 Unique해야 함
- 두 번째 인자 ‘fetchTodoList’. 쿼리 함수(Query Functions)라고 부름. 비동기 함수
- 쿼리 함수는 promise 객체를 return한다.
- promise 객체는 반드시 data를 resolve하거나 에러를 내야 해요.
오류가 발생한 경우에는 사용자가 오해하지 않도록 그에 맞는 적절한 오류 처리
ㄴtry~ catch문으로 감싸주기
ㄴ또는 비동기함수 호출하고 ().then().catch()
useQuery를 통해 얻은 결과물은 객체(object). 객체 안엔 조회 결과(isLoading, isError, isSuccess...)를 확인할 수 있음
Query Keys 쿼리 키
Query Key는 query invalidation 때문에 중요함.
이 이름을 가지고 useMutation에서 onSuccess가 됐을 때
이거를 다시 취소(invalidate)하고 다시 갱신된 값으로 업데이트.
배열 형태로 갖고 있어서 useQuery('todos', ...)의 쿼리 키는 queryKey === ['todos']로 해석됨
정보를 유일하게 식별하기 위해 하나의 단어보다 더 많은 ‘표현’이 필요하다면 문자, 숫자, object 등등 여러가지를 조합한 배열 형태의 key도 사용이 가능
(공식 문서)
// 💥주의! key는 표현이 그렇다는거지, api 로직과는 관련이 없어요!
// 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' }]
mutations
query와는 다르게 mutation은 CUD에서 사용
mutation.mutate( 인자 )
- 인자는 반드시 한 개의 변수 또는 객체여야 해요.
- mutation.mutate(인자1, 인자2) → 오류
결과를 객체(object 형태로) 갖고 있습니다.
그 결과물 객체는 항상 어느 상태 중 하나에 속해요.
- isIdle
- isLoading
- isError
error 객체를 항상 품고 있음을 명심! - isSuccess(query에만 있는게 아니에요)
data 객체를 항상 품고 있음을 명심
// [출처] : 공식문서
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>
)
}
'React' 카테고리의 다른 글
throttling & debouncing 처리하기 (0) | 2023.07.06 |
---|---|
Custom Hooks (0) | 2023.07.06 |
Thunk (0) | 2023.07.06 |
Axios (0) | 2023.07.06 |
json-server (0) | 2023.07.06 |