WEB/REACT

[React] react-query

자바칩 프라푸치노 2023. 3. 22. 12:12

1. react-query

React-Query는 React 애플리케이션에서 API 데이터를 쉽게 가져오고 관리할 수 있도록 도와주는 라이브러리입니다.

React-Query를 사용하려면, 먼저 React-Query 라이브러리를 설치해야합니다.

npm install react-query

다음으로, React-Query의 useQuery hook을 사용하여 데이터를 가져올 수 있습니다. useQuery는 3가지 인자를 받습니다.

  • queryKey: 요청에 사용할 고유 키입니다. 이 값은 문자열 또는 배열일 수 있습니다. 배열을 사용하면 여러 개의 쿼리를 처리할 수 있습니다.
  • queryFn: 실제 데이터를 가져오는 함수입니다. 이 함수는 Promise를 반환해야하며, 데이터를 가져오는 로직이 포함되어야합니다.
  • options: useQuery hook을 호출하는 데 사용되는 옵션 객체입니다.

예를 들어, 다음과 같이 사용할 수 있습니다.

 

import { useQuery } from 'react-query';

function App() {
  const { isLoading, error, data } = useQuery('todos', () =>
    fetch('https://jsonplaceholder.typicode.com/todos').then((res) =>
      res.json()
    )
  );

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>An error has occurred: {error.message}</div>;

  return (
    <div>
      <h1>Todos</h1>
      {data.map((todo) => (
        <p key={todo.id}>{todo.title}</p>
      ))}
    </div>
  );
}

위의 예제에서는 useQuery를 사용하여

JSONPlaceholder API에서 Todos 데이터를 가져오고, 로딩 상태를 표시하고, 오류를 처리하고, 데이터를 렌더링합니다.

React-Query는 다양한 기능과 옵션을 제공하여 데이터 가져오기와 관리를 훨씬 더 쉽게 만듭니다.

예를 들어, 데이터 캐싱, refetching, infinite scrolling 등을 쉽게 처리할 수 있습니다.

(get은 useQuery)

 

 

2. useMutation

useMutation hook은 React-Query의 기능 중 하나로,

API 호출을 위한 새로운 데이터를 만들거나, 기존 데이터를 업데이트 또는 삭제하는 등의 동작을 수행할 때 사용됩니다.

다음은 useMutation hook을 사용한 예제입니다.

이 예제에서는 특정 ID를 가진 사용자를 삭제하는 API를 호출하고, 성공 또는 실패 시 사용자에게 알림 메시지를 보여줍니다.

(post, update, delete는 useMutation)

 

import { useMutation } from 'react-query';

function App() {
  const [deleteUserId, setDeleteUserId] = useState('');
  const { mutate, isLoading, isSuccess, isError, error } = useMutation(
    (id) =>
      fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
        method: 'DELETE',
      }).then((res) => res.json()),
    {
      onSuccess: () => {
        alert('User deleted successfully!');
        setDeleteUserId('');
      },
      onError: (error) => {
        alert('Error deleting user: ' + error.message);
      },
    }
  );

  const handleDeleteUser = () => {
    mutate(deleteUserId);
  };

  return (
    <div>
      <h1>Delete User</h1>
      <label>
        User ID:
        <input
          type="number"
          value={deleteUserId}
          onChange={(e) => setDeleteUserId(e.target.value)}
        />
      </label>
      <button onClick={handleDeleteUser}>Delete</button>
      {isLoading && <div>Loading...</div>}
      {isSuccess && <div>User deleted successfully!</div>}
      {isError && <div>Error deleting user: {error.message}</div>}
    </div>
  );
}

위의 예제에서는 useMutation hook을 사용하여 mutate 함수를 만들고, 해당 함수를 클릭 이벤트 처리기에 연결합니다.

mutate 함수는 사용자가 입력한 ID를 인자로 전달하고, API에서 데이터를 삭제합니다.

useMutation hook은 isLoading, isSuccess, isError와 같은 여러 상태값을 반환하므로,

이를 활용하여 요청 상태에 따른 알림 메시지를 사용자에게 보여줄 수 있습니다.

onSuccessonError 콜백 함수를 사용하여 각각 성공과 실패 시의 처리를 할 수 있습니다.

 

 

 

아래는 위의 코드를 커스텀 훅으로 추상화한 코드입니다.

import { useMutation } from 'react-query';

function useDeleteUser() {
  const [isLoading, setIsLoading] = useState(false);
  const [isSuccess, setIsSuccess] = useState(false);
  const [isError, setIsError] = useState(false);
  const [error, setError] = useState(null);

  const deleteUser = useMutation(
    (id) =>
      fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
        method: 'DELETE',
      }).then((res) => res.json()),
    {
      onSuccess: () => {
        setIsLoading(false);
        setIsSuccess(true);
        setIsError(false);
        setError(null);
      },
      onError: (error) => {
        setIsLoading(false);
        setIsSuccess(false);
        setIsError(true);
        setError(error);
      },
    }
  );

  const handleDeleteUser = (id) => {
    setIsLoading(true);
    setIsSuccess(false);
    setIsError(false);
    setError(null);

    deleteUser.mutate(id);
  };

  return {
    isLoading,
    isSuccess,
    isError,
    error,
    handleDeleteUser,
  };
}

function App() {
  const [deleteUserId, setDeleteUserId] = useState('');
  const { isLoading, isSuccess, isError, error, handleDeleteUser } =
    useDeleteUser();

  return (
    <div>
      <h1>Delete User</h1>
      <label>
        User ID:
        <input
          type="number"
          value={deleteUserId}
          onChange={(e) => setDeleteUserId(e.target.value)}
        />
      </label>
      <button onClick={() => handleDeleteUser(deleteUserId)}>Delete</button>
      {isLoading && <div>Loading...</div>}
      {isSuccess && <div>User deleted successfully!</div>}
      {isError && <div>Error deleting user: {error.message}</div>}
    </div>
  );
}

위의 예제 코드를 useDeleteUser라는 custom hook으로 추상화하였습니다.

이렇게 추상화된 hook은 다른 컴포넌트에서도 사용할 수 있으며,

동일한 로직을 반복 작성할 필요가 없어 코드의 재사용성이 높아집니다.

 

useDeleteUser hook은 deleteUser mutation과 handleDeleteUser 함수를 반환합니다.

deleteUser mutation을 사용하여 API 호출을 수행하고,

handleDeleteUser 함수는 deleteUser.mutate를 호출하여 deleteUser mutation을 실행합니다.

이 함수는 isLoading, isSuccess, isError, error와 같은 상태값도 함께 반환합니다.

 

 

3. mutate

mutate 함수는 useMutation hook에서 반환되는 객체의 메서드 중 하나로,

mutation을 수동으로 트리거하는 데 사용됩니다.

mutate 함수는 mutation에 전달된 인수를 사용하여 API 호출을 수행하고, mutation 상태를 업데이트합니다.

mutate 함수는 다음과 같은 형태를 가집니다.

 

mutate(
  variables?: Variables, 
  options?: MutateOptions<Data, Error, Variables>
): Promise<Data>

 

  • variables: mutation에 전달될 변수입니다.
  • options: mutate 함수에 전달할 옵션입니다. useMutation hook의 options 객체와 유사합니다.
  • Promise<Data>: mutate 함수는 Promise를 반환하며, Promise가 성공하면 API에서 반환한 데이터를 포함한 객체가 반환됩니다.

mutate 함수에 variablesoptions를 전달하여 API 호출을 수행할 수 있습니다.

 

variables는 mutation에 전달할 인수를 담은 객체입니다.

optionsuseMutation hook의 options와 동일한 옵션을 가지며,

onSuccess, onError, onSettled와 같은 콜백 함수를 포함할 수 있습니다.

 

mutate 함수를 호출하면 React Query는 캐시된 데이터를 업데이트하고, 새로운 데이터를 가져와 캐시합니다.

이 때, useQuery hook과 마찬가지로 더 나은 사용자 경험을 위해 optimistic updates를 수행합니다.

이는 API 호출이 완료되지 않았을 때도 미리 데이터를 변경하여 UI를 업데이트함으로써 사용자가 보다 빠르게 응답하는 것을 가능하게 합니다.

 

 

코드 해석

export const useABCQuery = (params: ABCRequestParams) => {
  const queryKey = [ABC_API_PATH, params];

  return useQuery({
    queryKey,
    queryFn: () => getABCApi(params)
  })
}

 

useQuery hook은 첫 번째 인수로 query key를, 두 번째 인수로는 queryFn을 받습니다.

query key는 해당 쿼리를 식별하는 데 사용되며, 여기서는 [ABC_API_PATH, params]로 정의되어 있습니다.

이 값에 따라 유일한 식별자가 생성되어, 해당 쿼리에 대한 데이터를 캐싱하는 데 사용됩니다.

 

queryFn은 쿼리를 실행하는 함수로, 여기서는 getABCApi 함수를 사용하여 API를 호출하고 데이터를 반환합니다.

따라서 useABCQuery hook은 params에 따라  API에서 데이터를 가져와서

React Query의 캐시 기능을 사용하여 데이터를 관리하는 데 사용됩니다.

 

이를 통해 애플리케이션에서  데이터를 쉽게 관리할 수 있으며, 캐시 기능을 사용함으로써

네트워크 요청 수를 줄이고, 더 나은 성능을 제공할 수 있습니다.

 

 

4. 전역관리

React Query에서 관리하는 데이터는 전역으로 관리됩니다.

React Query는 컴포넌트 트리의 상위 레벨에서 QueryClient를 생성하고, 이를 사용하여 쿼리 결과를 저장하고 캐시합니다.

따라서 React Query를 사용하는 모든 컴포넌트는 동일한 QueryClient 인스턴스를 공유하여 데이터를 조회하고 업데이트할 수 있습니다.

 

이는 React Query가 동일한 데이터를 중복으로 불러오지 않도록 보장하고,

쿼리 결과를 캐시하고 업데이트하여 네트워크 요청 수를 줄이는 데 도움이 됩니다.

또한 React Query는 브라우저의 캐시와 같은 기존의 캐시 시스템을 보완하여 더욱 효율적인 데이터 관리를 제공합니다.

 

 

 

5. queryClient

import { QueryClient, QueryClientProvider, useQuery } from 'react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <div>
        <h1>Books</h1>
        <BookList />
      </div>
    </QueryClientProvider>
  );
}

function BookList() {
  const { isLoading, error, data } = useQuery('books', fetchBooks);

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.map((book) => (
        <li key={book.id}>
          <h2>{book.title}</h2>
          <p>{book.author}</p>
        </li>
      ))}
    </ul>
  );
}

async function fetchBooks() {
  const response = await fetch('/api/books');
  const data = await response.json();
  return data;
}

위 예제에서는 QueryClient를 생성하여 QueryClientProvider 컴포넌트에 전달합니다.

이 컴포넌트는 React Query의 쿼리 클라이언트를 프로바이더로 제공하여 애플리케이션 전체에서 쿼리를 관리합니다.

 

BookList 컴포넌트에서는 useQuery hook을 사용하여 fetchBooks 함수를 호출하고, 이를 book라는 쿼리 키로 캐시합니다. isLoadingerror 상태를 통해 데이터를 불러오는 동안 로딩 화면과 에러 메시지를 표시할 수 있습니다.

그리고 데이터가 로드되면 data 상태를 통해 결과를 렌더링합니다.

 

fetchBooks 함수에서는 실제로 /api/books API를 호출하여 데이터를 가져옵니다.

이 데이터는 QueryClient에 의해 캐시됩니다.

 

따라서 애플리케이션에서 책 목록을 요청할 때마다 서버에 다시 요청하지 않고, 이전에 가져온 데이터를 사용할 수 있습니다.

이와 같이 React Query의 QueryClient를 사용하면 쿼리 결과를 캐시하고 업데이트하여 더 나은 성능을 제공할 수 있습니다.

 

 

 

 

6. getQueryData, setQueryData

export const useABCQueryData = () => {
  const queryClient = useQueryClient()
  
  // getter
  const getQueryData = () => {
    return queryClient.getQueryData<ABCQueryData>(queryKey)
  }

  // setter
  const setQueryData = (updater: Updater<ABCQueryData | undefined, ABCQueryData | undefined>) => {
    queryClient.setQueryData(updater)
  }


  return {
    getQueryData,
    setQueryData
  }
}

위 코드는 React Query에서 useQueryClient hook을 사용하여 쿼리 클라이언트 객체를 가져와서 사용하는 예제입니다.

 

getQueryData 함수는 해당 쿼리 키에 대한 데이터를 가져오는 메서드입니다.

setQueryData 함수는 해당 쿼리 키에 대한 데이터를 설정하는 메서드입니다.

 

updater는 이전 데이터를 받아 새로운 데이터를 반환하는 함수이며, 업데이트를 수행하는 방법을 정의합니다.

useABCQueryData hook에서는 getQueryDatasetQueryData 함수를 반환합니다.

이 함수들을 사용하면 캐시된 데이터를 가져오고 업데이트할 수 있습니다.

 

 

import { useABCQuery } from 'path/to/useABCQuery';
import { useABCQueryData } from 'path/to/useABCQueryData';

function MyComponent() {
  const { data, isLoading } = useABCQuery();

  const { getQueryData, setQueryData } = useABCQueryData();

  const handleClick = () => {
    const currentData = getQueryData();

    // currentData에서 원하는 정보를 가공하여 새로운 데이터 생성
    const newData = { ...currentData, additionalInfo: 'some info' };

    // 기존 데이터 업데이트
    setQueryData(newData);
  };

  return (
    <>
      {isLoading && <div>Loading...</div>}
      {!isLoading && data && (
        <div>
          {/* 데이터 렌더링 */}
        </div>
      )}
      <button onClick={handleClick}>Update Data</button>
    </>
  );
}

위 코드에서는 useABCQuery hook을 사용하여 데이터를 가져오고,

useABCQueryData hook을 사용하여 setQueryDatagetQueryData 함수를 가져왔습니다.

 

handleClick 함수에서는 getQueryData 함수를 사용하여 현재 캐시된 데이터를 가져온 후,

해당 데이터를 가공하여 newData를 생성합니다.

그리고 setQueryData 함수를 사용하여 newData를 캐시에 업데이트합니다.

이렇게 setQueryDatagetQueryData 함수를 사용하면 쿼리 데이터를 가져오고 업데이트할 수 있습니다.

 

 

 

7. queryFn

queryFnuseQuery hook에서 데이터를 가져오는 함수를 지정하는 옵션입니다.

이 함수는 Promise를 반환해야 하며, 해당 Promise가 resolve되면 데이터가 캐시에 저장됩니다.

이후 같은 쿼리가 호출되면 캐시된 데이터가 반환됩니다.

 

queryFn은 보통 API 호출을 하여 데이터를 가져오는 함수로 사용됩니다.

API 호출을 하는 함수를 queryFn으로 지정하면, 해당 함수는 쿼리를 호출할 때마다 API를 호출하여 데이터를 가져옵니다.

이때, queryFn은 호출 시 필요한 인자값들을 받을 수 있습니다.

 

아래는 queryFn을 사용하여 API 호출을 하는 예시 코드입니다.

 

import { useQuery } from 'react-query';
import axios from 'axios';

interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

const fetchTodos = async (userId: number): Promise<Todo[]> => {
  const { data } = await axios.get(`https://jsonplaceholder.typicode.com/todos?userId=${userId}`);
  return data;
};

const MyComponent = () => {
  const { data, isLoading, error } = useQuery(['todos', 1], () => fetchTodos(1));

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>
          {todo.title} - {todo.completed ? 'completed' : 'not completed'}
        </li>
      ))}
    </ul>
  );
};

위 코드에서는 useQuery hook을 사용하여 API에서 todos 데이터를 가져옵니다.

queryKey로 ['todos', 1]을 사용하고, queryFn으로 fetchTodos 함수를 사용합니다.

fetchTodos 함수는 userId를 인자로 받으며, 해당 userId로 API를 호출하여 데이터를 가져옵니다.

fetchTodos 함수는 Promise를 반환하므로, 해당 Promise가 resolve되면 데이터가 캐시에 저장되고,

이후 같은 쿼리가 호출되면 캐시된 데이터가 반환됩니다.

 


참고 : 위 설명들은 챗 gpt가 설명해준 내용입니다.

728x90