1. React Query란?
1) 기존 미들웨어의 문제점?
이전까지 API 통신을 리덕스와 사용하기 위해서는 리덕스 툴킷의 Redux-thunk, 또는 Redux-saga라는 라이브러리를 채택해서 사용했다. 하지만 이 두가지는 리덕스의 라이브러리이고, 리덕스는 전역상태관리 전용이기때문에 다수의 API 통신에 사용하기에는 어려움이 있었다.
다수의 컴포넌트에서 동일한 API를 호출하거나, 특정 컴포넌트에서의 API응답이 다른 컴포넌트의 API 응답에 영향을 미치는 경우 등 사용자의 시나리오에 대응하기가 적합하지 않았다.
코드량이 너무 많고 복잡할 뿐더러 리덕스가 비동기 데이터 관리를 전문적으로 해주지 않아 이러한 라이브러리를 사용할 때 규격화가 잘 안되어 있어 오류를 발생시키기도 했다.
redux toolkit 의 등장으로 코드가 조금은 줄어들긴 했으나... 여전히 엄청난 양의 보일러플레이트가 .......
이러한 문제점을 보완하기위해 리액트 쿼리가 등장하게 되었다.
https://tech.kakaopay.com/post/react-query-1/
2) React Query의 장점
리액트 쿼리는 리액트 앱에서 서버의 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트 하는 작업을 도와주는 라이브러리이다. 이는 사용방법이 thunk 대비 매우 간결하며 코드가 직관적이며 클라이언트 상태를 분리해서 관리할 수 있기 때문에 효율적이다.
기존의 리덕스를 사용할 때는 컴포넌트에서 원하는데 스토어의 데이터를 가져오기 위해 useEffect에서 비동기 함수처리를 한 뒤 미들웨어를 통해 dispatch 하는 방식이었다.
하지만 react-query를 사용한다면 이렇게 useQuery 단 한줄로 바꿀 수 있다 (useEffect 안 써도 됨) 👻
export default function App() {
const { data } = useQuery(["data"], getServerData);
return <div>{JSON.stringify(data)}</div>;
즉 useEffect를 사용하지 않기 때문에 Side effect를 제거할 수 있다는 장점과 전체적인 흐름을 어렵게 만들지 않는다는 장점이 있다...!!!!
또한 비동기 처리들을 동기적으로 처리하는 것 처럼 구현할 수 있다. 만약 어떤 비동기 처리 이후의 조건이 처리되어야만 그 다음 비동기 처리를 수행한다. 라는 조건이 있을 때 enabled 속성을 추가하면 간편하다.
const { data: data1 } = useQuery(["data1"], getServerData);
const { data: data2 } = useQuery(["data2", data1], getServerData, {
enabled: !!data1
});
2. react-query 사용방법
yarn add react-query
react-query 라이브러리를 설치한 다음 App.js 에서 clientQuery를 생성하고 설정해준다.
import Router from "./shared/Router";
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<Router />
</QueryClientProvider>
);
};
export default App;
이제 프로젝트 전체에서 queryClient를 사용할 수 있다.
1) 조회 READ
api를 관리하는 api 폴더에서 todos.js를 생성한다.
import axios from "axios";
export const getTodos = async () => {
const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
return response.data;
};
그리고나서 데이터 조회가 필요한 TodoList에서 useQuery를 사용한다. (두번째 인자에 getTodos 전달)
function TodoList({ isActive }) {
const { isLoading, isError, data } = useQuery("todos", getTodos);
if (isLoading) {
return <h1>Loading...</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} />;
})}
</StyledTodoListBox>
</StyledDiv>
);
}
useQuery에서 data뿐만 아니라 isLoading, isError 또한 연산해주기 때문에 매우 편리하다.
갖고있던 todos 데이터가 잘 보인다. 👻
2) 추가 (CREATE)
api.js에 add 비동기함수를 추가한다.
// axios 요청이 들어가는 모든 모듈
import axios from "axios";
const getTodos = async () => {
const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
return response.data;
};
//데이터 추가
const addTodos = async (newTodo) => {
return await axios.post(`${process.env.REACT_APP_SERVER_URL}/todos`, newTodo);
};
export { getTodos, addTodos };
addTodos를 정의하고, 이제 post 요청을 날릴 이벤트핸들러를 가지고 있는 Input.jsx로 가보자
handleSubmitButtonClick 함수인데 이 안의 유효성검사 부분 코드가 배울점이 많아서 일단 다 가져와봤다...
덕분에 중복내용 어떻게 검사하는지 알게 되었다.
const handleSubmitButtonClick = (event) => {
event.preventDefault();
// "01" : 필수 입력값 검증 실패 안내
if (!title || !contents) {
return getErrorMsg("01", { title, contents });
}
// 이미 존재하는 todo 항목이면 오류
const validationArr = todos.filter(
(item) => item.title === title && item.contents === contents,
);
// "02" : 내용 중복 안내
if (validationArr.length > 0) {
return getErrorMsg("02", { title, contents });
}
const newTodo = {
title,
contents,
isDone: false,
id: uuidv4(),
};
mutation.mutate(newTodo);
setTitle("");
setContents("");
};
아무튼 모든 유효성검사를 마치면 mutation.mutate(newTodo)를 통해 데이터가 추가된다.
새로운 데이터로 화면을 갱신하기 위해 이전의 query를 invalidate 하기 위해
const mutation = useMutation(addTodos, {
onSuccess: () => {
queryClient.invalidateQueries("todos"); // 화면 갱신
// const { isLoading, isError, data } = useQuery("todos", getTodos);
// 이 코드에서 query key를 "todos"로 줬었음
},
}); //첫번째 파라미터는 함수, 두번째는 객체 (성공 or 실패)
위의 코드를 추가한다.
이때 query key를 잘 기억해뒀다가 invalidate 할 때 인자로 해당 query key를 전달해주면 된다.
const { data: todos } = useQuery("todos", getTodos);
조회 코드에서의 key도 마찬가지이다.
*useQuery의 인자
1) Query Keys
- refetching할 때 쓰임 ( =>queryClient.invalidateQueries("QUERY_KEY");
- 캐싱처리할 때 쓰임
- 애플리케이션 전체 맥락에서 이 쿼리를 공유하는 방법으로 쓰임 <- 어느 컴포넌트에 뿌려져있어도 같은 key면 같은 데이터 사용
- 배열 형태로도 사용 가능 (문자, 숫자, object를 조합하여)
- 어쨌든 unique 해야함
// ID가 5dls todo item 1개인데, preview 속성은 true임
useQuery(['todo',5, { preview: true }], ... )
// todolist 전체인데, type은 done임
useQuery(['todos', { type: 'done' }, ...])
이렇게 원하는 쿼리 키를 직관적으로 작성할수도 있다.
2) Query Functions
- 두번째 인자로 promise 객체를 리턴하는 비동기 함수를 넣음
- 반드시 data를 resolve하거나 reject 해야함
- useQuery 가 반환하는 것은 isLoading, isError, data 가 담긴 객체이다. 각 객체의 키를 잘 핸들해야함
* mutations
- CUD에서 사용된다.
- 첫번째 인자로 비동기 함수가 들어옴
const mutation = useMutation(addTodos, {
onSuccess: () => {
queryClient.invalidateQueries("todos"); // 화면 갱신
// const { isLoading, isError, data } = useQuery("todos", getTodos);
// 이 코드에서 key를 "todos"로 줬었음
},
}); //첫번째 파라미터는 함수, 두번째는 객체 (성공 or 실패)
query key 통일시키기!
const newTodo = {
title,
contents,
isDone: false,
id: uuidv4(),
};
mutation.mutate(newTodo);
Reference :
'Computer Programming > React' 카테고리의 다른 글
React axios instance 사용법 (API 모듈화) (0) | 2023.07.18 |
---|---|
리액트 기능구현 스터디- 3. Infinite Scroll 리팩토링 회고 (vs react-virtualized)☀️ (0) | 2023.07.10 |
리액트가 Virtual DOM을 사용하는 이유? (1) | 2023.07.05 |
axios instance와 interceptor | toolkit + thunk로 API 비동기 처리 (0) | 2023.07.03 |
React 기능 구현 스터디 - 2.검색 기능 (리팩토링 회고) ☀️ (0) | 2023.07.03 |