React Query를 배워보자

lovjgb·2022년 8월 30일
0

React Query

yarn add react-query
  • 서버 상태를 잘 관리하기 위한 상태 관리 라이브러리이다.
  • 비동기 처리를 편히 할 수 있도록 많은 기능을 제공한다.
  • 공식문서

React Query의 상태 관리 흐름

1. fetching

  • 데이터 요청 중

2. fresh

  • 데이터를 갓 받아온 직후
  • 컴포넌트의 상태가 변하더라도 데이터 재요청을 하지 않는다.

3. stale

  • 데이터 만료
  • 최신화가 필요한 데이터

4. inactive

  • 쿼리가 언마운트 된 상태(더는 사용하지 않는 상태)
  • ★쿼리가 언마운트 된다고 해서 비동기 요청이 취소되는 것은 아니다. 프라미스가 일단 만들어지고 언마운트 된 것이라면 데이터는 캐시에 살아있을 수 있다.

5. delete

  • 완전히 삭제된 상태(캐시 데이터가 메모리에서 삭제된 상태)

주요 개념

Query? query key+ query function

  • query key
    • 쿼리를 구분하기 위한 특정 값, 문자, 배열, 딕셔너리 등을 넣을 수 있다.
  • query function
    • 서버에서 데이터를 요청하고 promise를 리턴하는 함수(=비동기 요청함수)
  • data
    • 쿼리 함수가 리턴한 promise에서 resolve(해결된)된 데이터
  • staleTime
    • 쿼리 데이터가 fresh에서 stale로 전환되는 데 걸리는 시간. 기본값은 0이다.
  • cacheTime
    • unused 또는 inactive 캐시데이터가 메모리에서 유지될 시간
    • 기본값은 5분, 설정한 시간을 초과하면 메모리에서 제거된다.

query instance(useQeury) 사용법

  • useQuery는 어떤 데이터를 가져와서 캐싱하기 위해서 쓴다.
    • 쿼리키, 쿼리함수, 옵션 으로 구성되어 있다.
    • 쿼리키 : 문자열/배열 등을 넘길 수 있다. 이 키는 캐싱 처리하는데에 사용한다. 불러다 쓰기 위해서도 중요하다.
    • 쿼리 함수 : promise를 리턴하는 어떠한 함수를 넘겨줘야 된다. api와 통신하는 함수가 주로 들어간다.
    • 옵션 : useQuery()를 위한 옵션들을 넣어주면 된다.
const my_first_query = useQuery(쿼리 키, 쿼리 함수, 옵션);

usequery에 담기는 데이터

  • data : 서버에 요청한 데이터
  • isLoading : 캐시가 없는 상태에서의 데이터 요청중인 상태(true/false)
  • isFetching : 캐시의 유무 상관 없이 데이터 요청중인 상태(true/false)
  • isError : 서버 요청 실패에 대한 상태(true/false)
  • error : 서버 요청 실패(object)
  • refetch : 데이터를 새로 패칭하는 함수

알아두면 좋을 usequery 옵션

  • cacheTime : 언마운트된 후 어느 시점까지 메모리에 데이터를 저장하여 캐싱할 것인지를 결정
    기본 값 : 30000(5분)
    • Infinity로 무한대 캐싱 가능
  • staleTime : 쿼리가 fresh 상태에서 stale 상태로 전환되는 시간
    기본 값 : 0
    • 자주 바뀌지 않는 페이지는 조금더 높게 설정하자~
  • refetchOnMount : 컴포넌트 마운트시 새로운 데이터 패칭
    기본 값 : true (false일 경우엔 마운트해도 새로운 데이터를 가지고 오지 않아요.)
  • refetchOnWindowFocus : 브라우저 클릭 시 새로운 데이터 패칭
    기본 값 : true (false일 경우엔 다른 탭으로 갔다가 돌아와도 데이터를 새로 가져오지 않아요.)
  • refetchInterval : 지정한 시간 간격만큼 데이터 패칭 / 브라우저 바깥에 있을 경우(다른 탭에 있는 등) 실행되지 않아요!
    기본 값 : 0
  • refetchIntervalInBackground : 브라우저에 포커스가 없어도 refetchInterval에서 지정한 시간 간격만큼 데이터 패칭
    기본 값 : false
  • enabled : 컴포넌트가 마운트 되어도 데이터 패칭 ❌
    기본 값 : true
    useQuery의 리턴 값 중 refetch가 있었죠! enabled가 true일 때는 refetch를 통해 데이터를 패칭해야 합니다.
    이 옵션은 useQuery가 useEffect마냥 처음에 무조건 실행할 것인지를 설정하는 옵션이다.
    Default는 true로 무조건 실행이 되는데 유저의 클릭이벤트 등에 사용할때 false로 설정 후 refetch를 이용해 사용할 수 있다.
  • onSuccess : 데이터 패칭 성공 시 콜백 함수
  • onError : 데이터 패칭 실패 시 콜백
  • initialData : 초기 데이터
    쿼리 생성 전이나 아직 캐싱되지 않았을 경우, 이 데이터를 초기 데이터로 써요!
  • select : 데이터 패칭 성공 시 가져온 데이터를 변환해주고 싶을 때, 원하는 데이터 형식으로 변환하기 위한 콜백 (여기서 return으로 변환한 데이터를 반환해주면 useQuery의 결과값인 data가 변해요.)
  • keepPreviousData
    아까 말했듯이 query key의 데이터를 처음 fetching할때 isLoading이 true이다.
    근데 isLoading 대신에 유저가 다른곳에서 받아온 방금 전 데이터를 그냥 보여주고 fetching해와서 업데이트 하고 싶다면 이 옵션을 사용하면 된다.
    아니 이런 옵션을 대체 왜 필요하고 어디서 씀? 이라고 생각할 수 있는데 pagination 클릭했을때 안에 목록만 쓰윽 바꾸고 싶을때 사용하면 된다. 유저가 2페이지 보고 있다가 3페이지를 눌렀을때 로딩 애니메이션을 보여주는게 아니라 2페이지의 데이터를 보여주다가 새롭게 받아온 3페이지의 데이터로 쓰윽 갈아치울때 사용하면 된다. Gmail 같은 느낌 생각하면 될듯.
    Default는 false
  • useInfiniteQuery
    스크롤이 하단에 다다랐을때 우리는 새롭게 fetching을 해와야 할 때가 있다.
    인스타 돋보기 처럼 일정부분되면 fetching 해오고 싶을 때 또는 load more 등의 버튼을 눌러서 새롭게 업데이트 할 때이다.
    useInfiniteQuery는 사용법이 좀 다르고 약간 복잡

https://velog.io/@jewoo/React-Query-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0

유용한 팁

  • 다중호출
    • 그냥 여러개 호출 하면된다. 쿼리 키로 구분하여 사용하면 끝..
  • 동기적 호출
    • 순서대로 API 호출이 필요할 때는 어떻게 할까?
      enabled: data!!로 꼼수를 부리면 된다.
      1번 API 호출 성공후 2번 API 해야되면 1번 API 데이터값을 2번 enabled에 넣어준다.
{
  	enabled: postData!!
}

사용해보기

1. QueryClientProvider 설정하기

//index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <QueryClientProvider client={queryClient}>
    {/* devtools */}
    <ReactQueryDevtools initialIsOpen={true} />
    <App />
  </QueryClientProvider>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

2. 첫번째 query instance 만들기 : useQuery!

//query.jsx
import { useQuery } from "react-query";
import axios from "axios";

export const getSleepData = async () => {
  try {
    const { data } = await axios.get("http://localhost:3001/sleep_times");
    console.log(data);
    return data;
  } catch (err) {
    console.log(err);
  }
};

export const addSleepData = async (payload) => {
  try {
    const { data } = await axios.post("http://localhost:3001/sleep_times", payload);
    console.log(data);
    return data;
  } catch (err) {
    console.log(err);
  }
};

export const deleteSleepData = async (payload) => {
  try {
    console.log(payload);
    const { data } = await axios.delete(`http://localhost:3001/sleep_times/${payload}`);
    console.log(data);
    return data;
  } catch (err) {
    console.log(err);
  }
};

export const updateSleepData = async (payload) => {
  try {
    const { data } = await axios.patch(`http://localhost:3001/sleep_times/${payload.id}`, { day: payload.day, sleep_time: payload.sleep_time });
    console.log(data);
    return data;
  } catch (err) {
    console.log(err);
  }
};
//main component.jsx
import React from "react";
import styled from "styled-components";
import { useMutation, useQuery, useQueryClient } from "react-query";
import axios from "axios";
import { getSleepData, addSleepData, deleteSleepData, updateSleepData } from "../hooks/Query";

const Home = ({ sleepData }) => {
  console.log(sleepData);
  const day_input = React.useRef("");
  const time_input = React.useRef("");
  const queryClient = useQueryClient();

  // 첫번째 자리 : 키값 , 두번째 : 속성, 세번째 : 옵션
 // const my_first_query = useQuery(쿼리 키, 쿼리 함수, 옵션);
  const sleep_query = useQuery("sleep_list", getSleepData(), {
    onSuccess: (data) => {
      console.log(data.data);
    },
    staleTime: 10000,
  });
  //화면 밖에 나갔다가 오면 다시 Api를 불러오는 옵션이 있다.
  //fresh time을 조절해주면 된다.

  // const sleep_mutation = useMutation(addSleepData);
  // const { mutate: addMutateData } = useMutation(addSleepData, {
  const { mutate: addMutateData } = useMutation((data) => addSleepData(data), {
    onSuccess: (data) => {
      console.log(data);
      queryClient.setQueryData("sleep_list", (sleep_times) => {
        return [...sleep_times, data];
      });

      //방법1
      // key를 넣지 않을 경우 모든 쿼리가 무효화됩니다.
      // mutation을 성공하면 수면 데이터 목록을 불러오는 useQuery를 무효화 시켜줍니다!
      // post후 바로 fetch해주기 위해! usequery를 무효화 시켜서 수면 데이터 목록을 다시 불러오기~
      // queryClient.invalidateQueries("sleep_list");
      // day_input.current.value = "";
      // time_input.current.value = "";
    },
    onError: (error) => {
      console.log(error);
    },
  });
  //mutate => muatation을 동작하게 해주는 함수!

  const addDataHandler = () => {
    if (day_input.current.value === "" || time_input.current.value === "") {
      return;
    }
    const data = { day: day_input.current.value, sleep_time: time_input.current.value };
    console.log(data);
    addMutateData(data);
  };

  // const { mutate: updateSleep } = useMutation((data) => updateSleepData(data), {
  const { mutate: updateSleep } = useMutation((data) => updateSleepData(data), {
    onSuccess: () => {
      queryClient.invalidateQueries("sleep_list");
      day_input.current.value = "";
      time_input.current.value = "";
    },
    onError: (error) => {
      console.log(error);
    },
  });

  const { mutate: deleteSleep } = useMutation((data) => deleteSleepData(data), {
    onSuccess: () => {
      queryClient.invalidateQueries("sleep_list");
    },
    onError: (error) => {
      console.log(error);
    },
  });

  const updateDataHandler = (dataId) => {
    const data = { id: dataId, day: day_input.current.value, sleep_time: time_input.current.value };
    updateSleep(data);
  };

  const deleteDataHandler = (dataId) => {
    deleteSleep(dataId);
  };

  //불러오는 중에 data가 없을 것이므로 isLoading null을 해주면 console.찍을 때 에러가 안나게 될것이다.
  if (sleepData.isLoading) {
    return null;
  }

  return (
    <>
      Home
      {sleepData.map((d) => {
        console.log(d);
        return (
          <div key={d.id}>
            <p>{d.day}</p>
            <p>{d.sleep_time}</p>
            <button onClick={() => updateDataHandler(d.id)}>수정하기</button>
            <button onClick={() => deleteDataHandler(d.id)}>삭제하기</button>
          </div>
        );
      })}
      <input ref={day_input} />
      <input ref={time_input} />
      <button onClick={addDataHandler}>데이터 추가하기</button>
    </>
  );
};

export default Home;
profile
lovjgb

0개의 댓글