[React][사용예정] React-Query 라이브러리

Katie·2023년 5월 10일
0

사이드프로젝트

목록 보기
10/13

이전 프로젝트에서는 axios와 fetch를 사용해서 비동기를 구현하였다. 리액트로 개발을 하면서 좀 더 다양한 라이브러리를 사용하기 위해 이번 프로젝트에서는 React Query를 사용할 예정이다.

공식 문서

React Query란
서버 상태 관리 라이브러리로 비동기 로직을 쉽게 다루게 해준다.

  • 서버 상태 가져오기, 캐싱, 동기화 및 업데이트를 쉽게 만들어 준다.
  • 데이터를 주고받은 결과를 캐시로 저장하여, 동일한 요청이 반복되어도 새로운 요청을 보내지 않고 저장되어 있는 데이터를 사용할 수 있습니다.
  • 네트워크 사용량을 줄이고, 애플리케이션 성능을 향상시키는 데 도움이 된다.
  • react-query의 키를 통하여 값을 저장하여 사용자가 다시 같은 페이지에 접속한다면 api 재요청을 하지 않고 값을 가져와 보여주어 로딩속도가 향상된다.

공식 문서 예시
import React from "react";
import ReactDOM from "react-dom/client";
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from "react-query";
import { ReactQueryDevtools } from "react-query-devtools";
import axios from "axios";

const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  );
}

function Example() {
  const { isLoading, error, data, isFetching } = useQuery({
    //캐싱하고 다시 불러오기 위한 키 값
    queryKey: ["repoData"], 
    //queryKey: "repoData", 
    //콜백 함수를 실행시킨 값을 캐싱해서 저장하고 hook처럼 사용 가능
    queryFn: () =>
      axios
        .get("https://api.github.com/repos/tannerlinsley/react-query")
        .then((res) => res.data),
  },
  {
    refetchInterval: 5000, //옵션값 - 5초마다 다시 불러오는 기능
  });

  if (isLoading) return "Loading...";

  if (error) return "An error has occurred: " + error.message;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
      <strong>👀 {data.subscribers_count}</strong>{" "}
      <strong>{data.stargazers_count}</strong>{" "}
      <strong>🍴 {data.forks_count}</strong>
      <div>{isFetching ? "Updating..." : ""}</div>
      <ReactQueryDevtools initialIsOpen />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);

useQuery

  • useQuery는 리액트 훅과 유사한 구조를 가지고 있다.
  • get 하기 위한 api로 useQuery를 사용한다.
    - post와 update는 useMutation을 사용
  • 비동기로 작동
  • 한 컴포넌트에 여러 개의 useQuery가 있다면 두 개의 useQuery가 동시에 실행된다.
  • 여러 개의 useQuery를 사용할 시 useQueries 권장
const { data, isError, isLoading, error } = useQuery(queryKey, queryFn, options)
  • data: 서버 요청에 대한 데이터
  • isLoading: 캐시가 없는 상태에서 데이터를 요청 중인 상태
  • isError: 서버 요청 실패에 대한 상태
  • error: 서버 요청 실패

  • queryKey: 설정한 unique Key로 다른 컴포넌트에서도 호출 가능하다.
  • queryFn: 비동기 함수 (Promise)

Options

const { isLoading, error, data, isFetching } = useQuery({
    queryKey: ["repoData"], 
    queryFn: () =>
      axios
        .get("https://api.github.com/repos/tannerlinsley/react-query")
        .then((res) => res.data),
  },
  {
    refetchInterval: 5000, //옵션값 - 5초마다 다시 불러오는 기능
  	efetchOnWindowFocus: false, // 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아와서 함수 재실행 여부 옵션
  //default 값: true
    retry: 0, // 실패한 쿼리 재시도 옵션 default로 3번 재시도
  			//true=무한 재시도, false=재시도 X
  staleTime: 60000, // 1분 동안 데이터가 fresh 상태로 유지됨. 이후에는 stale 상태
  //fresh 상태: 쿼리가 다시 mount 되어도 fetch 실행X
  //default: 0
    cacheTime: 300000, // 5분 동안 inactive 상태인 캐시 데이터가 메모리에 남아있음 이후 메모리에서 제거
  //default 값 = 5분
    refetchOnMount: 'always', // stale 상태일 경우 매번 마운트 시 refetch 실행
  //defualt=true
  
    onSuccess: data => {
      // 성공시 호출
      console.log(data);
    },
    onError: e => {
      // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됩니다.)
      // 강제로 에러 발생시키려면 api단에서 throw Error 날립니다. (참조: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default)
      console.log(e.message);
    }
  });

status로 isLoading, isSuccess 한 번에 처리하기

const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList)

const { status, data, error } = useQuery("todos", fetchTodoList);

if (status === "loading") {
    return <span>Loading...</span>;
  }

  if (status === "error") {
    return <span>Error: {error.message}</span>;
  }

useQuery 동기적 실행

const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const { data: nextTodo, error, isFetching } = useQuery(
  "nextTodos",
  fetchNextTodoList,
  {
    enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
  }
);

useQueries

const usersQuery = useQuery("users", fetchUsers);
const teamsQuery = useQuery("teams", fetchTeams);
const projectsQuery = useQuery("projects", fetchProjects);

// 아래 예시는 롤 룬과, 스펠을 받아오는 예시입니다.
const result = useQueries([
  {
    queryKey: ["getRune", riot.version],
    queryFn: () => api.getRunInfo(riot.version)
  },
  {
    queryKey: ["getSpell", riot.version],
    queryFn: () => api.getSpellInfo(riot.version)
  }
]);

useEffect(() => {
  console.log(result); // [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}]
  const loadingFinishAll = result.some(result => result.isLoading);
  console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료
}, [result]);

useMutation

값을 바꿀 때(update나 post) 사용하는 api이다.

import { useState, useContext, useEffect } from "react";
import loginApi from "api";
import { useMutation } from "react-query";

const Index = () => {
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");

  const loginMutation = useMutation(loginApi, {
    onMutate: variable => {
      console.log("onMutate", variable);
      // variable : {loginId: 'xxx', password; 'xxx'}
    },
    onError: (error, variable, context) => {
      // error
    },
    onSuccess: (data, variables, context) => {
      console.log("success", data, variables, context);
    },
    onSettled: () => {
      console.log("end");
    }
  });

  const handleSubmit = () => {
    loginMutation.mutate({ loginId: id, password });
  };

  return (
    <div>
      {loginMutation.isSuccess ? "success" : "pending"}
      {loginMutation.isError ? "error" : "pending"}
      <input type="text" value={id} onChange={e => setId(e.target.value)} </>
      <input
        type="password"
        value={password}
        onChange={e => setPassword(e.target.value)}
      />
      <button onClick={handleSubmit}>로그인</button>
    </div>
  );
};

export default Index;

값 업데이트 후 get 실행하기

const mutation = useMutation(postTodo, {
  onSuccess: () => {
    // postTodo가 성공하면 todos로 맵핑된 useQuery api 함수를 실행합니다.
    queryClient.invalidateQueries("todos");
  }
});
  • mutation에서 return 된 값으로 get 파라미터를 변경해야하는 경우 setQueryData 사용
const queryClient = useQueryClient();

const mutation = useMutation(editTodo, {
  onSuccess: data => {
    // data가 fetchTodoById로 들어간다
    queryClient.setQueryData(["todo", { id: 5 }], data);
  }
});

const { status, data, error } = useQuery(["todo", { id: 5 }], fetchTodoById);

mutation.mutate({
  id: 5,
  name: "nkh"
});

React Suspense와 React-query

// src/index.js
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 0,
      suspense: true
    }
  }
});

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

const { data } = useQurey("test", testApi, { suspense: true });

return (
  // isLoading이 true이면 Suspense의 fallback 내부 컴포넌트가 보여집니다.
  // isError가 true이면 ErrorBoundary의 fallback 내부 컴포넌트가 보여집니다.
  <Suspense fallback={<div>loading</div>}>
    <ErrorBoundary fallback={<div>에러 발생</div>}>
      <div>{data}</div>
    </ErrorBoundary>
  </Supense>
);

참고 블로그

profile
이것 저것 코딩일지 쓰는 프론트엔드 코린이

0개의 댓글