본문 바로가기
프로그래밍/react

react query) 너무나 쉬운 리액트 비동기관리

by 한코코 2022. 8. 13.

설치

$ npm i @tanstack/react-query

 

 

 세팅

QueryClinet 인스턴스를 만들어준 후,

사용하고 싶은 디렉토리의 최상위 컴포넌트를 QueryClientProvider로 감싼다.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

const App = ({ Component, pageProps }) => {
  return (
    <QueryClientProvider client={queryClient}>
      <Header />
      <Component {...pageProps} />
    </QueryClientProvider>
  );
};

export default App;

 


[ useQuery : get 요청 보내기 / 데이터 읽기]

useQuery에서 쓰이는 queryKey & queryFn

// 1
const res = useQuery(queryKey, queryFn);

// 2
const res = useQuery({
    queryKey: queryKey,
    queryFn: queryFn
});

 

 

queryKey

useQueryn마다 부여되는 고유 Key값이고 문자열이나 배열 형태로 사용된다.

문자열로 작성되었을 경우는 자동으로 길이가 1인 배열로 인식된다.

const res = useQuery('persons', queryFn); // 길이 1
const res = useQuery(['persons'], queryFn); // 길이 1

// 순서가 다르게 되면 안의 요소가 같아도 서로 다른 배여로 인식한다
const res = useQuery(['persons', 'add Id'], queryFn);
const res = useQuery(['add Id', 'persons'], queryFn);

const res = useQuery(['persons', {type: 'add', name: 'Id'}], queryFn);

 

 

queryFn

query function을 줄인 말로 promise 처리가 이루어지는 함수다.

fetch나 axios를 이용해 서버에 API를 요청하는 코드.

API 함수처리를 해서 가져와서 넣어도 되지만 따로 만들지 않았다면 직접 입력해도 문제없다.

const { data, error, status } = useQuery(
    // queryKey
    ["repoData"],
    
    // queryFn, get 요청을 하고 있다
    () => fetch("http://localhost:4000/list/view").then((res) => res.json())
  );
  ...
}

 

 

 

 

 

리액트 쿼리에게 기본적으로 주어진 상태들 : data, error, status...

data : success 상태값을 가지고 있으며, useQuery가 성공적으로 실행되었을때 data 속성을 통해 값을 사용할 수 있다.

error : isError 상태값을 가지고 있으며, useQuery가 실패했을때 error 속성을 통해 오류를 사용할 수 있다.

status : isLoading, isError 상태값을 가지고 있으며 각각 쓰기 귀찮을때 한꺼번에 쓰려고 만들어놓았다고 보면 됨.

 

const { data, error, status } = useQuery(...)

isLoading과 isError는 항상 boolean 값을 반환한다.

if (isLoading) return "Loading...";
if (isError) {
  return <span>Error: {error.message}</span>;
}

boolean값을 쓰기 싫으면 status를 사용하면 된다.

if (status === "loading") return "Loading...";
if (status === "success") return "실행에 성공함!!";
if (status === "error") {
  return <span>Error: {error.message}</span>;
}

 

 

 

 

 

staleTime & cacheTime

오래된 데이터를 stale한 데이터라고하고, react query는 stale한 데이터를 보여주는 것을 싫어한다.

그래서 언제나 fresh한 데이터를 달라고 서버에게 요구한다. ( = refetch 수행 )

staleTime의 default값이 0초이기때문에 react query는 계속해서 refresh를 시도한다.

 

cacheTime은 캐싱처리가 이루어지는 시간을 의미하며 default값은 5분이다.

queryKey에 매핑되는 데이터가 사용되지 않는 시점을 기준으로 5분 안에 queryKey를 요구하면 이전의 데이터를 보여준다.

5분이 지나면 캐시 가비지 콜렉터 타이머가 실행되어 기존 데이터가 삭제처리가 된다.

이때 queryKey를 다시 호출하면 서버에 데이터를 다시 요청하게 된다.

 

useQuery는 staleTime과 cacheTime 둘 다 가지고 있으므로,

하나라도 만족하지 않으면 서버에 다시 데이터를 요구한다.

( 참고 블로그 : https://jforj.tistory.com/243 )

 

 

 

 

 

 


[ useMutation : post 요청 보내기 / 데이터 생성, 업데이트, 삭제]

useQuery에 쓰이는 mutationFn & mutate

useMutation으로 mutation 객체를 정의하고, mutate 메서드를 사용하면 요청 함수를 호출해 요청이 보내진다.

// 1
const mutation = useMutation(mutationFn);

// 2
const mutation = useMutation({
    mutationFn: mutationFn
})

 

 

 

mutationFn

mutation Function으로 promise 처리가 이루어지는 함수.

fetch나 axios를 이용해 서버에 API를 요청하는 코드.

API 함수처리를 해서 가져와서 넣어도 되지만 따로 만들지 않았다면 직접 입력해도 문제없다.

const mutation = useMutation(newTodo => {
  return axios.post('/todos', newTodo)
})

 

 

 

mutate

useMutation을 이용해 작성한 내용들이 실행될 수 있도록 도와주는 trigger 역할을 한다.

useMutaion을 정의해 둔 뒤 이벤트가 발생되었을때 mutate를 사용해주면 된다.

const CreateTodo = () => {
  // useMutation으로 만든 이벤트
  const mutation = useMutation(formData => {
    return fetch('/api', formData)
  })
  
  const onSubmit = event => {
    event.preventDefault()
    
    // 정의된 useMutation을 실행하는 mutate
    mutation.mutate(new FormData(event.target)) 
  }

  return <form onSubmit={onSubmit}>...</form>
}

 

 

 

 

리액트 쿼리에게 기본적으로 주어진 상태들 : data, error, status...

data : success 상태값을 가지고 있으며, mutation이 성공적으로 실행되었을때 data 속성을 통해 값을 사용할 수 있다.

error : isError 상태값을 가지고 있으며, mutation이 실패했을때 error 속성을 통해 오류를 사용할 수 있다.

status : isLoading, isError 상태값을 가지고 있으며 각각 쓰기 귀찮을때 한꺼번에 쓰려고 만들어놓았다고 보면 됨.

idle : isIdle 상태값을 가지고 있으며, mutation이 현재 idle( 어떠한 프로그램에 의해서도 사용되지 않는 상태 = 유휴상태 )이거나 fresh/reset state다.

 

단, mutation 함수는 비동기 함수이므로 React 16 및 이전 버전의 event callback에서 직접 사용할 수 없다.

onSubmit시 event에 접근해야하는 경우, 그냥 사용하면 이벤트 풀링이 일어나므로 다른 함수로 mutate을 감싸야 함.

이벤트 풀링 : 이벤트를 처리하기 위해 제공하는 이벤트 리스터의 인자(event, e)가 매번 초기화가 되는 현상

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>
  )
}

 

 

 

 

mutation.reset

mutation request에서 error나 data를 지우고싶을때 reset 메서드를 사용하면 된다.

{ mutation.error && (
  <h5 onClick={() => mutation.reset()}>{ mutation.error }</h5>
)}

 

 

 

 

 


데이터 업데이트하기

invalidateQueries

useQuery는 새로운 데이터가 쌓여도 정해진 시간이 도달하지 않으면 상태값을 재렌더링을 하지 않는다.

useQuery의 staleTime과 cacheTime 개념 때문인데,

invalidateQueries는 서버로부터 데이터를 다시 조회해오기 위해서 queryKey의 유효성을 제거해준다.

캐싱된 데이터를 화면에 보여주지 않고 서버에 새롭게 데이터를 요청하게 되므로 재생성된 데이터를 화면에서 볼 수 있다.

const createAppMutataion = useMutation(createAppAPI, {
  onSuccess: (data) => queryClient.invalidateQueries(createAppAPI),
});

 

 

 

setQueryData

기존에 queryKey에 매핑되어있는 데이터를 새롭게 정의해준다.

// useMutate 정의
const savePerson = useMutation(person =>
  axios.post('http://localhost:8080/savePerson', person), {
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
});

* 기본적으로 react query 공식 사이트를 참고하였다. *

( 공식 사이트 : https://tanstack.com/query/v4/docs/guides/queries )

댓글