Pandaman Blog

[React Query] Query / Mutation 이해하기 본문

Front end/React-Query

[React Query] Query / Mutation 이해하기

oyg0420 2022. 4. 11. 23:03

1. Query 상태 흐름 이해하기

https://codesandbox.io/s/react-query-sangtae-heureum-oyig4o

 

react-query(상태 흐름) - CodeSandbox

react-query(상태 흐름) by oyg0420 using axios, react, react-dom, react-query, react-scripts, styled-components, typescript

codesandbox.io

해당 예제는 쿼리 상태 흐름을 이해하기 위한 예제이다.

function usePosts() {
  return useQuery("posts", async () => {
    const { data } = await axios.get(
      "https://jsonplaceholder.typicode.com/posts"
    );
    return data;
  });
}

function usePost(postId) {
  return useQuery(["post", postId], () => getPostById(postId));
}

두 개의 Hook을 살펴보면 이름에서 알 수 있듯이, 상위의 hook은 리스트를 fetching, 아래는 하나의 post를 fetching 한다.

기본 옵션은 아래와 같다.

staleTime = 0 
cacheTime = 5분
refetchOnMount = true
retry = 3
refetchOnMount = true
refetchOnWindowFocus = true

페이지를 진입하면 usePosts가 실행되고 "posts" 쿼리는 fetching 상태가 된다. staleTime = 0 이므로 바로 stale 상태가 된다.

만약 옵션에 staleTime을 10000(10초)으로 설정한다면 10초 동안은 fresh 상태가 되고 staleTime이 지나면 stale 상태가 된다.

{ staleTime: 10000 }

이제 포스트 리스트 중 하나를 선택해보자. 화면에서 리스트는 안 보이게 되며, "posts" 쿼리는 inactive 상태가 된다. cacheTime이 만료되면 Garbage Collector에 의해 "posts" 쿼리는 메모리에서 제거된다. 눈으로 확인하고 싶다면 직접 cacheTime을 설정하고 확인할 수 있다.

{ cacheTime: 10000 }

포스트 리스트 중 하나(가장 위에꺼를 선택)를 선택하면 "['post', 1]" query 가 fetching 되고 stale 상태가 된다.

만약 다른 탭에서 다시 돌아오면 fetcing 상태가 되고 stale 된다. 또한 브라우저를 클릭해도 동일한 현상이 발생한다. 아래 이미지는 방금 말한 active 상태의 query의 상태 흐름이다.

2. Auto Refetching / Polling / Realtime

이 예제는 3가지의 기능에 대해 설명하는 예제이다.

https://codesandbox.io/s/auto-refetching-mj967e

 

Auto Refetching - CodeSandbox

Auto Refetching by oyg0420 using axios, isomorphic-unfetch, next, react, react-dom, react-query

codesandbox.io

첫 번째는 Auto Refetching이다. 예제를 이해한 바로는 useMutaion을 활용해서 생성과 삭제 시 자동으로 refetching 하는 기능을 설명한 예제로 판단된다. 생성/수정/삭제 시에 자동 refetcing 기능을 사용하기 위해서는 mutaion과 invalidateQueries 메서드를 이해해야 한다.

Mutaion 이해하기

서버의 데이터를 fetching 하는 것이 아니라, 생성/수정/삭제 시에는 useMutaion hook을 사용해야 한다. useMutaion(mutationFn, options을)은 인자로 mutationFn과 options을 받는다. mutationFn는 생성/수정/삭제 API를 호출 함수 할당한다. options에는 성공, 실패 등 다양한 콜백 함수를 할당할 수 있다. useMutaion 반환하는 객체의 메서드인 mutate로 mutationFn를 호출한다. mutate 인자는 mutationFn의 매개변수로 전달된다.

 const addMutation = useMutation((value) => fetch(`/api/data?add=${value}`));

 addMutation.mutate(value);

addMutation.mutate(value);가 실행되면 useMutation(value) => fetch("/api/data?add=${value}")가 실행된다.

그리고 다시 fetch 버튼을 클릭하면 todos 쿼리를 refetch 하면 목록을 호출한다.

Auto Refetching

매번 fetch 버튼을 클릭해서 목록의 최신화하는 것이 번거롭다. 이때 등장하는 녀석이 있다. 바로 invalidateQueries 이다.
공식문서에 따르면 invalidateQueries 는 쿼리들을 stale로 상태를 변경하고 refetch 하도록 한다.

그럼 코드에서 어떻게 해야 할까?

const addMutation = useMutation((value) => fetch(`/api/data?add=${value}`), {
  onSuccess: () => queryClient.invalidateQueries("todos")
});

onSuccess는 성공 시에 동작하는 콜백 함수이다.  따라서 예제에서는 등록 후 바로 fetch 하여 최신의 데이터를 볼 수 있다.

invalidateQueriesrefetch의 다른 점은 무엇일까?
refetch는 쿼리에 대한 observer가 없더라도 refetch는 항상 refetch를 하지만 invalidateQueriesquery를 stale 상태로 변경하고 마운트 되어야 refetch를 하게 된다는 차이점이 있다.
refetch vs invalidating query

 

refetch vs invalidating query · Discussion #2468 · tannerlinsley/react-query

Hello! In the documentation how to invalidate queries after mutations I can see that it is suggested to use query invalidation. I'm curious what if instead of invalidation use refetching ? Won&...

github.com

https://codesandbox.io/s/refetch-vs-invalidatequeries-9gc30j?file=/pages/index.js​ 

 

refetch vs invalidateQueries - CodeSandbox

refetch vs invalidateQueries by oyg0420 using axios, isomorphic-unfetch, next, react, react-dom, react-query

codesandbox.io

Polling

"폴링(polling)이란 하나의 장치(또는 프로그램)가 충돌 회피 또는 동기화 처리 등을 목적으로 다른 장치(또는 프로그램)의 상태를 주기적으로 검사하여 일정한 조건을 만족할 때 송수신 등의 자료처리를 하는 방식을 말한다."

실시간처럼 보이는 클라이언트가 서버에게 일정한 주기를 가지고 응답을 주고받는 방식을 폴링 방식이라고 한다. react-query에서는 refetchInterval 옵션으로 polling 방식을 실현시킬 수 있다.

  const { status, data, error, isFetching } = useQuery(
    "todos",
    async () => {
      const res = await axios.get("/api/data");
      return res.data;
    },
    { refetchInterval: intervalMs }
  );

RealTime

해당 예제에서 서로 다른 탭에서도 데이터를 업데이트했을 때 실시간으로 반영되는 것을 확인할 수 있다. 이는 refetchInterval 옵션과 더불어 refetchOnMount, refetchOnWindowFocus 옵션 덕분에 다른 탭에서도 실시간으로 업데이트된 데이터를 확인할 수 있다.

3. Optimistic Updates

한국어로 번역하면 Optimistic Updates는 '낙관적 업데이트'이다. 이게 무슨 말이지... 사실 한 번도 들어본 적도 사용해본 적도 없는 말이다. Optimistic Updates를 이해하기 위해 어떤 행동을 하는지 확인해보자.
서버 업데이트를 하기 전에 미리 화면에서 해당 쿼리 데이터를 변경해서 화면에서 표시한 후, 서버와의 통신 결과에 따라 해당 쿼리의 데이터를 확정 / 롤백을 결정하는 방식이다.

예제를 살펴보자.

https://codesandbox.io/s/optimistic-update-962bs9?file=/pages/index.js:670-1899 

 

Optimistic Update - CodeSandbox

Optimistic Update by oyg0420 using axios, isomorphic-unfetch, next, react, react-dom, react-query

codesandbox.io

해당 예제는 todo 를 생성하면 해당 todo 리스팅 반영된다. 아래는 todo 항목이 추가될 때의 사이드이팩트 함수들이다.

      // Optimistically update the cache value on mutate, but store
      // the old value and return it so that it's accessible in case of
      // an error
      onMutate: async (text) => {
        setText("");

        // 1. Cancel any outgoing refetches (so they don't overwrite our optimistic update)
        await queryClient.cancelQueries("todos");

        // 2. Snapshot the previous value
        const previousValue = queryClient.getQueryData("todos");

        // 3. Optimistically update to the new value
        if (previousValue) {
          queryClient.setQueryData("todos", {
            ...previousValue,
            items: [...previousValue.items, text]
          });
        }

        return { previousValue };
      },
      // On failure, roll back to the previous value
      onError: (err, variables, context) => {
        if (context.previousTodos) {
          queryClient.setQueryData("todos", context.previousValue);
        }
      },
      // After success or failure, refetch the todos query
      onSettled: () => {
        queryClient.invalidateQueries("todos");
      }

todo 항목이 추가되면 'onMutate' 콜백 함수가 실행된다.

  1. queryClient.cancelQueries를 통해서 해당 query의 refetch를 취소한다(실제로 요청한 api를 취소하지 않고 새롭게 가져온 데이터를 해당 query에 set 하지 않음). optimistic update가 이뤄지기 전에 실제로 서버에서 가저온 데이터가 화면에 있는 데이터의 오버라이트 하는 일을 방지하기 위함이다.
  2. queryClient.getQueryData("todos") 로 현재의 값을 가져온다.
  3. queryClient.setQueryData를 이용해 현재 값에 새로운 값을 추가한다. 이를 optimistic update 라고 한다.
  4. 마지막으로 이전 값을 반환한다. 이는 onError 콜백 함수에서 사용하기 위함이다.

실제로 요청 서버에서 업데이트가 실패한다면, 우리는 이전 값으로 롤백해야 한다. 롤백에 대한 로직은 onError 콜백 함수에서 작성할 수 있다.

  1. 매개변수 context는 onMutate에서 반환한 값을 포함한다. 따라서, onMutate 에서 우리는 previousValue를 반환했으며 queryClient.setQueryData("todos", context.previousValue)와 같이 이전 값으로 데이터를 롤백할 수 있게 되었다.

업데이트가 성공, 실패 여부 상관없이 실제로 todo 리스트에 대한 데이터를 가져온다.

  1. onSettled 콜백 함수는 마지막으로 동작하는 콜백 함수이다. queryClient.invalidateQueries("todos") 통해서 todos 쿼리를 refetch 한다.

사실 해당 예제에서 알 수 있는 점은 react-query에서 이런 사이드이팩트를 처리할 수 있는 기능들을 제공하고 있다는 점에서 redux-saga와 비슷하다고 생각이 든다. redux-saga에서는 'onMutate', 'onError', 'onSettled'에 해당하는 action 들을 모두 생성할 수 있고, dispatch를 통해서 해당 action 이 발생할 때 동작하는 saga에 대한 로직을 작성할 수 있다. 하지만 이런 코드의 양이 상당하다. react-query의 useMutaition의 sideeffect callback 함수들은 상당히 간편하고 쉽게 사용이 가능하다는 장점이 존재하는 것 같다.

'Front end > React-Query' 카테고리의 다른 글

[React Query] Default Query Function  (0) 2022.05.15
[React Query] Pagination과 Infinite Scroll  (0) 2022.05.14
Comments