React

React Query

차돌박이츄베릅 2023. 7. 6. 13:08

청크&사가(기존) -> 리액트 쿼리(요새 많이 쓰는듯)

리액트 쿼리는 모듈 건드리지 않고 함. 보일러 플레이트 만들다가 오류날 일이 없음

리액트 쿼리의 사용방법이 thunk 대비 쉽고 직관적임

 

키워드

  • Query: 어떤 데이터에 대한 요청
  • Mutation: 어떤 데이터를 변경
  • Query Invalidation: Query를 invalidation. 즉, 무효화 시킨다
    기존에 가져온 Query는 서버 데이터이기 때문에, 언제든지 변경이 있을 수 있어요.
    그렇기 때문에 최신 상태가 아닐 수 있습니다.
    그런 경우, 기존의 쿼리를 무효화 시킨 후 최신화 시켜야 하겠죠.
    이런 과정을 React Query에서는 알아서 해준답니다

 

준비

  1. yarn add axios
  2. yarn add json-server
  3. db.json파일 만들기(json-server npm사이트에서 샘플 가져오기)
  4. 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의 인자

  1. 첫 번째 인자 ‘todos’. 쿼리의 키(Query Keys)라고 부름
    • refetching 하는 데에 쓰여요.
    • 캐싱(caching) 처리를 하는 데에도 쓰이죠.
    • 애플리케이션 전체 맥락에서 이 쿼리를 공유하는 방법으로 쓰여요. —> 그 어느 컴포넌트 곳곳에 촥촥 뿌려져 있어도 같은 key면 같은 쿼리 및 데이터 보장!!
    • Query keys는 Unique해야 함
  2. 두 번째 인자 ‘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 형태로) 갖고 있습니다.

 

그 결과물 객체는 항상 어느 상태 중 하나에 속해요.

  1. isIdle
  2. isLoading
  3. isError
    error 객체를 항상 품고 있음을 명심!
  4. 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