React Query 한글 메뉴얼

column clash·2021년 9월 29일
145

공식 홈페이지 https://react-query.tanstack.com/guides/window-focus-refetching
의 내용을 한글로 옮김.

작성목적은 한국어로 된 자료가 없어서,
라이브러리를 익힐 겸 정리해보았습니다.


메뉴얼을 정리한 후, 느낀 점은,
React Query 에서 세세한 설정등이 많아서,
한번에 익히기 보다는,

심플 예제,

import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const queryClient = new QueryClient();

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

function Example() {
  const { isLoading, error, data, isFetching } = useQuery("repoData", () =>
    fetch(
      "https://api.github.com/repos/tannerlinsley/react-query"
    ).then((res) => res.json())
  );

  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.render(<App />, rootElement);

낙관적 업데이트
https://codesandbox.io/s/github/tannerlinsley/react-query/tree/master/examples/optimistic-updates?from-embed

를 보고, custom-hooks 예제를 참고해서 hook 으로
만드는 것만 참고해서 사용한 후,
사용빈도에 따라 세부 설정등을 작업 해보는 것이 좋겠다라는
생각을 했습니다.


React Query

React를 위한 강력하고 성능 좋은 데이터 동기화
"전역 상태"를 건드리지 않고도 React 및 React Native 애플리케이션에서 데이터를 가져오고, 캐시하고, 업데이트할 수 있습니다.

선언적 및 자동

데이터 가져오기 로직을 ​​손으로 작성하는 것은 끝났습니다. React Query에 데이터를 가져올 위치와 데이터가 얼마나 필요한지 알려주면 나머지는 자동입니다. React Query는 구성이 필요 없는 즉시 캐싱, 백그라운드 업데이트 및 오래된 데이터를 처리합니다.

간단하고 친숙한

promise 또는 async/await로 작업하는 방법을 알고 있다면 이미 React Query를 사용하는 방법을 알고 있는 것입니다. 관리할 전역 상태, 감속기, 정규화 시스템 또는 이해해야 할 무거운 구성이 없습니다. 데이터를 해결하는(또는 오류를 발생시키는) 함수를 전달하기만 하면 나머지는 기록입니다.

강력하고 구성 가능

React Query는 모든 사용 사례에 맞는 노브와 옵션을 사용하여 쿼리의 각 관찰자 인스턴스까지 구성할 수 있습니다. 전용 devtools, 무한 로딩 API 및 데이터 업데이트를 쉽게 만들어주는 일류 mutation 도구와 연결되어 있습니다. 걱정하지 마십시오. 모든 것이 성공을 위해 미리 구성되어 있습니다!

적은 코드. 더 적은 엣지 케이스.

리듀서, 캐싱 로직, 타이머, 재시도 로직, 복잡한 비동기/대기 스크립팅(계속 진행할 수 있습니다...) 대신 문자 그대로 평소 하던 코드의 아주 작은 부분을 작성합니다. React Query를 사용할 때 작성하는 코드가 얼마나 적은지 또는 얼마나 많은 코드를 삭제하는지에 놀랄 것입니다.

설치 : yarn add react-query

제작자가 사용법을 알려주는 유튜브 주소 : https://youtu.be/PPvWXbSCtBU (추천)

개요

React Query는 종종 React에 대한 누락된 데이터 가져오기 라이브러리로 설명되지만 보다 기술적인 용어로 말하면 React 애플리케이션에서 서버 상태 를 가져오고, 캐싱하고, 동기화하고, 업데이트 하는 작업을 쉽게 만듭니다 .

동기 부여
기본적으로 React 애플리케이션 에는 구성 요소에서 데이터를 가져오거나 업데이트하는 독창적인 방법 이 제공되지 않으므로 개발자는 데이터를 가져오는 고유한 방법을 구축하게 됩니다. 이는 일반적으로 React 후크를 사용하여 구성 요소 기반 상태와 효과를 함께 꼬집거나 더 범용 상태 관리 라이브러리를 사용하여 앱 전체에 비동기 데이터를 저장하고 제공하는 것을 의미합니다.

대부분의 기존 상태 관리 라이브러리는 클라이언트 상태 작업에 적합하지만 비동기 또는 서버 상태 작업에는 그다지 좋지 않습니다 . 이것은 서버 상태가 완전히 다르기 때문 입니다. 우선 서버 상태:

제어하거나 소유하지 않는 위치에 원격으로 유지됩니다.
가져오기 및 업데이트를 위한 비동기 API 필요
공유 소유권을 의미하며 사용자 모르게 다른 사람이 변경할 수 있습니다.
주의하지 않으면 응용 프로그램에서 잠재적으로 "오래된" 상태가 될 수 있습니다.
애플리케이션에서 서버 상태의 특성을 파악 하면 진행하면서 더 많은 문제가 발생 합니다. 예를 들면 다음과 같습니다.

캐싱... (아마도 프로그래밍에서 가장 어려운 일)
동일한 데이터에 대한 여러 요청을 단일 요청으로 중복 제거
백그라운드에서 "오래된" 데이터 업데이트
데이터가 "오래된" 경우 파악
가능한 한 빨리 데이터 업데이트 반영
페이지 매김 및 지연 로딩 데이터와 같은 성능 최적화
서버 상태의 메모리 및 가비지 수집 관리
구조적 공유를 통한 쿼리 결과 메모
그 목록에 압도되지 않는다면 그것은 아마도 당신이 이미 모든 서버 상태 문제를 해결했고 상을 받을 자격이 있음을 의미해야 합니다. 그러나 대다수의 사람들과 같다면 아직 이러한 문제의 전부 또는 대부분을 해결하지 못했거나 표면만 긁고 있을 뿐입니다!

React Query는 서버 상태를 관리하기 위한 최고의 라이브러리 중 하나입니다 . 구성 없이 즉시 사용 가능하고 놀라울 정도로 잘 작동 하며 애플리케이션이 성장함에 따라 원하는 대로 사용자 정의할 수 있습니다 .

React Query를 사용하면 서버 상태 의 까다로운 문제와 장애물을 극복하고 극복하고 앱 데이터가 사용자를 제어하기 시작하기 전에 앱 데이터를 제어할 수 있습니다.

좀 더 기술적인 부분에서 React Query는 다음과 같은 기능을 할 것입니다.

애플리케이션에서 복잡하고 오해의 소지가 있는 여러 줄의 코드 를 제거 하고 몇 줄의 React Query 로직으로 대체할 수 있도록 도와주세요.
새로운 서버 상태 데이터 소스 연결에 대한 걱정 없이 애플리케이션을 유지 관리하기 쉽고 새로운 기능을 더 쉽게 구축할 수 있습니다.
애플리케이션을 그 어느 때보다 빠르고 반응성이 좋게 만들어 최종 사용자에게 직접적인 영향을 미칩니다.
잠재적으로 대역폭을 절약하고 메모리 성능을 높이는 데 도움이 됩니다.
충분한 이야기, 이미 코드를 보여주세요!

빠른 시작

이 예제는 React Query의 3가지 핵심 개념을 매우 간략하게 보여줍니다.

쿼리
돌연변이
쿼리 무효화

import {
   useQuery,
   useMutation,
   useQueryClient,
   QueryClient,
   QueryClientProvider,
 } from 'react-query'
 import { getTodos, postTodo } from '../my-api'
 
 // Create a client
 const queryClient = new QueryClient()
 
 function App() {
   return (
     // Provide the client to your App
     <QueryClientProvider client={queryClient}>
       <Todos />
     </QueryClientProvider>
   )
 }
 
 function Todos() {
   // Access the client
   const queryClient = useQueryClient()
 
   // Queries
   const query = useQuery('todos', getTodos)
 
   // Mutations
   const mutation = useMutation(postTodo, {
     onSuccess: () => {
       // Invalidate and refetch
       queryClient.invalidateQueries('todos')
     },
   })
 
   return (
     <div>
       <ul>
         {query.data.map(todo => (
           <li key={todo.id}>{todo.title}</li>
         ))}
       </ul>
 
       <button
         onClick={() => {
           mutation.mutate({
             id: Date.now(),
             title: 'Do Laundry',
           })
         }}
       >
         Add Todo
       </button>
     </div>
   )
 }
 
 render(<App />, document.getElementById('root'))

타입스크립트

React Query는 이제 TypeScript 로 작성되어 라이브러리와 프로젝트가 유형이 안전한지 확인합니다!

type Group = {
  name? : string,
  age : number
}
function useGroups() {
   return useQuery<Group[], Error>('groups', fetchGroups)
 }

기본 설정

중요한 기본값
기본적 으로 React Query는 공격적이지만 정상적인 기본값 으로 구성됩니다 .

변경 옵션
staleTime :
더 길게 지정하면 staleTime쿼리가 데이터를 자주 다시 가져오지 않습니다.

refetchOnWindowFocus :

예상치 못한 리패치가 실행될 시,
refetchOnMount, refetchOnWindowFocus, refetchOnReconnect와 refetchInterval
로 제어할 수 있습니다.

cacheTime :

기본적으로 "비활성" 쿼리는 5분 후에 가비지 수집 됩니다.
이를 변경하려면 cacheTime쿼리 의 기본값 을 1000 60 5밀리초가 아닌 다른 값 으로 변경할 수 있습니다 .

retry, retryDelay
실패한 쿼리는 자동으로 3번 재시도
오류 나타내기전에 롤백에 관한 옵션

Queries

import { useQuery } from 'react-query'
 
 function App() {
   const info = useQuery('todos', fetchTodoList)
 }

쿼리는 고유키 (예시에서 todos) 에 연결된 비동기 데이터 소스 단위로 작동됩니다.
쿼리는 서버에서 데이터를 가져오기 위해 모든 Promise 기반 메서드(GET 및 POST 메서드 포함) 에 대해 사용이 가능합니다. (SWR 은 GET요청)

MODIFY (수정) 요청을 보낼때는, Mutations 를 사용하는 것이 좋습니다 .

기본 사용법. LOADING 과 ERROR 처리도 다음과 같이 작동합니다.

function Todos() {
   const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)
 
   if (isLoading) {
     return <span>Loading...</span>
   }
 
   if (isError) {
     return <span>Error: {error.message}</span>
   }
 
   // We can assume by this point that `isSuccess === true`
   return (
     <ul>
       {data.map(todo => (
         <li key={todo.id}>{todo.title}</li>
       ))}
     </ul>
   )
 }

isLoading 이나 isError 대신 status 을 활용할 수도 있습니다.

function Todos() {
   const { status, data, error } = useQuery('todos', fetchTodoList)
 
   if (status === 'loading') {
     return <span>Loading...</span>
   }
 
   if (status === 'error') {
     return <span>Error: {error.message}</span>
   }
 
   // also status === 'success', but "else" logic works, too
   return (
     <ul>
       {data.map(todo => (
         <li key={todo.id}>{todo.title}</li>
       ))}
     </ul>
   )
 }

Query Keys

핵심적으로 React Query는 쿼리 키를 기반으로 쿼리 캐싱을 관리합니다. 쿼리 키는 문자열처럼 단순할 수도 있고 많은 문자열과 중첩된 개체의 배열처럼 복잡할 수도 있습니다. 쿼리 키가 직렬화 가능 하고 쿼리 데이터에 고유한 한 사용할 수 있습니다

문자열 전용 쿼리 키

키의 가장 간단한 형태는 실제로 배열이 아니라 개별 문자열입니다. 문자열 쿼리 키가 전달되면 쿼리 키의 유일한 항목으로 문자열을 사용하여 내부적으로 배열로 변환됩니다. 이 형식은 다음에 유용합니다.

일반 목록/색인 리소스
비계층적 리소스

// 할 일 목록
 useQuery ( 'todos' , ... ) // queryKey === ['todos']  
 
 // 다른 것, 무엇이든!
 useQuery ( 'somethingSpecial' , ... ) // queryKey === ['somethingSpecial']  

배열 키

쿼리에 데이터를 고유하게 설명하기 위해 추가 정보가 필요한 경우 문자열이 있는 배열과 직렬화 가능한 개체를 사용하여 설명할 수 있습니다. 다음과 같은 경우에 유용합니다.

계층적 또는 중첩된 리소스
항목을 고유하게 식별하기 위해 ID, 인덱스 또는 기타 프리미티브를 전달하는 것이 일반적입니다.
추가 매개변수가 있는 쿼리
추가 옵션의 개체를 전달하는 것이 일반적입니다.

 // An individual todo
 useQuery(['todo', 5], ...)
 // queryKey === ['todo', 5]
 
 // And individual todo in a "preview" format
 useQuery(['todo', 5, { preview: true }], ...)
 // queryKey === ['todo', 5, { preview: true }]
 
 // A list of todos that are "done"
 useQuery(['todos', { type: 'done' }], ...)
 // queryKey === ['todos', { type: 'done' }]

쿼리 키는 순서에 관계없이 다음 쿼리는 모두 동일한 것으로 간주됩니다.

useQuery(['todos', { status, page }], ...)
 useQuery(['todos', { page, status }], ...)
 useQuery(['todos', { page, status, other: undefined }], ...)

그러나 다음 쿼리 키는 같지 않습니다. 배열 항목 순서가 중요합니다!

useQuery ( [ '할 일' , 상태 , 페이지 ] , ... ) 
 useQuery ( [ '할 일' , 페이지 , 상태 ] , ... ) 
 useQuery ( [ 'todos' , undefined , page , status ] , ... )  

쿼리 기능이 변수에 의존하는 경우 쿼리 키에 포함합니다.

function Todos({ todoId }) {
   const result = useQuery(['todos', todoId], () => fetchTodoById(todoId))
 }

Query Functions

쿼리 함수는 말 그대로 Promoise 을 return하는 모든 함수일 수 있습니다 .
return 된 Promoise 은 데이터 를 가져오거나 error를 발생 시켜야 합니다 .

다음은 모두 유효한 쿼리 기능 구성입니다.

useQuery(['todos'], fetchAllTodos)
 useQuery(['todos', todoId], () => fetchTodoById(todoId))
 useQuery(['todos', todoId], async () => {
   const data = await fetchTodoById(todoId)
   return data
 })
 useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]))

Handling and Throwing Errors (에러처리)

const { error } = useQuery(['todos', todoId], async () => {
if (somethingGoesWrong) {
throw new Error('Oh no!')
}

return data
})

fetch 로 작업을 한다면

axios 등의 유틸리티는 자동으로이 실패 HTTP 호출에 대한 오류를 던져주지만, fetch 는 해주지 않는다. 직접 처리를 하는 코드.

 useQuery(['todos', todoId], async () => {
   const response = await fetch('/todos/' + todoId)
   if (!response.ok) {
     throw new Error('Network response was not ok')
   }
   return res

병렬 쿼리 (Parallel Queries)

"병렬" 쿼리는 병렬로 실행되거나 동시에 가져오기 동시성을 최대화하기 위해 실행되는 쿼리입니다.

수동 병렬 쿼리
병렬 쿼리의 수가 변경되지 않으면 병렬 쿼리를 사용 하기 위한 추가 노력 이 없습니다 . 원하는 만큼의 React Query useQuery와 useInfiniteQuery후크를 나란히 사용하세요!

function App () {
   // The following queries will execute in parallel
   const usersQuery = useQuery('users', fetchUsers)
   const teamsQuery = useQuery('teams', fetchTeams)
   const projectsQuery = useQuery('projects', fetchProjects)
   ...
 }

동적 병렬 쿼리

function App({ users }) {
   const userQueries = useQueries(
     users.map(user => {
       return {
         queryKey: ['user', user.id],
         queryFn: () => fetchUserById(user.id),
       }
     })
   )
 }

종속 쿼리

종속(또는 직렬) 쿼리는 실행하기 전에 완료해야 하는 이전 쿼리에 의존합니다. enabled쿼리 옵션을 사용하는 것만 하면 될정도로 쉽습니다 .

// Get the user
 const { data: user } = useQuery(['user', email], getUserByEmail)
 
 const userId = user?.id
 
 // Then get the user's projects
 const { isIdle, data: projects } = useQuery(
   ['projects', userId],
   getProjectsByUser,
   {
     // The query will not execute until the userId exists
     enabled: !!userId,
   }
 )
 
 // isIdle will be `true` until `enabled` is true and the query begins to fetch.
 // It will then go to the `isLoading` stage and hopefully the `isSuccess` stage :)

Background Fetching Indicators

status === 'loading' 는 로딩상태를 보여주기에는 좋지만,
쿼리가 백그라운드에서 다시 가져오고 있다는 추가 표시기를 표시할 수 없습니다.
쿼리가 백그라운드에서 다시 가져온다는 것을 보여주기 위해서는
isFetching 을 사용하면 됩니다.

function Todos() {
   const { status, data: todos, error, isFetching } = useQuery(
     'todos',
     fetchTodos
   )
 
   return status === 'loading' ? (
     <span>Loading...</span>
   ) : status === 'error' ? (
     <span>Error: {error.message}</span>
   ) : (
     <>
       {isFetching ? <div>Refreshing...</div> : null}
 
       <div>
         {todos.map(todo => (
           <Todo todo={todo} />
         ))}
       </div>
     </>
   )
 }

Displaying Global Background Fetching Loading State

import { useIsFetching } from 'react-query'
 
 function GlobalLoadingIndicator() {
   const isFetching = useIsFetching()
 
   return isFetching ? (
     <div>Queries are fetching in the background...</div>
   ) : null
 }

Window Focus Refetching

사용자가 애플리케이션을 떠나 오래된 데이터로 돌아오면 React Query는 백그라운드에서 자동으로 새로운 데이터를 요청합니다 . 다음 refetchOnWindowFocus옵션을 사용하여 전역적으로 또는 쿼리별로 비활성화할 수 있습니다 .

 //
 const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
       refetchOnWindowFocus: false,
     },
   },
 })
 
 function App() {
   return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
 }

이 옵션을 활용할때는 보통 이렇게 쓰입니다.

useQuery('todos', fetchTodos, { refetchOnWindowFocus: false })

쿼리 비활성화/일시 중지

쿼리가 자동으로 실행되지 않도록 하려면 enabled = false옵션을 사용할 수 있습니다 .

enabled이 false일때 :

  • 쿼리에 캐시된 데이터가 있는 경우 :
    쿼리는 status === 'success'또는 isSuccess상태 에서 초기화됩니다 .

  • 쿼리에 캐시된 데이터가 없는 경우
    쿼리는 status === 'idle'또는 isIdle상태 에서 시작됩니다 .

  • 쿼리는 마운트 시 자동으로 가져오지 않습니다.

  • 새 인스턴스가 마운트되거나 새 인스턴스가 나타날 때 쿼리가 백그라운드에서 자동으로 다시 가져오지 않습니다.

  • 쿼리는 쿼리 클라이언트 invalidateQueries와 refetchQueries일반적으로 쿼리를 다시 가져오는 호출을 무시합니다 .

  • refetch 가져올 쿼리를 수동으로 트리거하는 데 사용할 수 있습니다.

function Todos() {
   const {
     isIdle,
     isLoading,
     isError,
     data,
     error,
     refetch,
     isFetching,
   } = useQuery('todos', fetchTodoList, {
     enabled: false,
   })
 
   return (
     <>
       <button onClick={() => refetch()}>Fetch Todos</button>
 
       {isIdle ? (
         'Not ready...'
       ) : isLoading ? (
         <span>Loading...</span>
       ) : isError ? (
         <span>Error: {error.message}</span>
       ) : (
         <>
           <ul>
             {data.map(todo => (
               <li key={todo.id}>{todo.title}</li>
             ))}
           </ul>
           <div>{isFetching ? 'Fetching...' : null}</div>
         </>
       )}
     </>
   )
 }

쿼리 재시도

useQuery쿼리가 실패시 어떻게 동작할지에 대한 설정에 대한 챕터입니다.
기본값은 3번이며 기본적인 설정에서는
연속 재 시도의 최대 수에 도달하지 않은 경우 쿼리가 자동으로 쿼리를 다시 시도합니다

전역 수준과 개별 쿼리 수준 모두에서 재시도를 구성할 수 있습니다.

  • retry = false하면 재시도가 비활성화됩니다.
  • retry = 6은 함수에서 발생한 최종 오류를 표시하기 전에 실패한 요청을 6번 재시도합니다.
  • retry = true하면 실패한 요청을 무한히 재시도합니다.
  • retry = (failureCount, error) => ...은 요청이 실패한 이유에 따라 사용자 정의 논리를 허용합니다.
import { useQuery } from 'react-query'
  
 const result = useQuery(['todos', 1], fetchTodoListPage, {
   retry: 10, 
 })

Retry Delay

기본적으로 React Query의 재시도는 요청이 실패한 직후에 발생하지 않습니다. 표준에 따라 백오프 지연은 각 재시도에 점진적으로 적용됩니다.

기본값 retryDelay은 1000시도할 때마다 두 배( ms 에서 시작)로 설정 되지만 30초를 초과할 수 없습니다.

 // Configure for all queries
 import { QueryCache, QueryClient, QueryClientProvider } from 'react-query'
 
 const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
       retryDelay: 시도인덱스 => Math.min(1000 * 2 ** 시도인덱스, 30000),
     },
   },
 })
 
 function App() {
   return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
 }

권장되지는 않지만 retryDelay공급자 및 개별 쿼리 옵션 모두에서 함수/정수를 분명히 재정의할 수 있습니다. 함수 대신 정수로 설정하면 지연 시간은 항상 동일합니다.

const result = useQuery('todos', fetchTodoList, {
   retryDelay: 1000, 
 })

Paginated / Lagged Queries

페이지가 있는 데이터는 매우 일반적인 UI 이며,
React Query에서는 쿼리 키에 페이지 정보를 포함하면 "그냥 작동"합니다.

const result = useQuery(['projects', page], fetchProjects)

그러나 이 간단한 예제를 실행하면 이상한 점을 발견할 수 있습니다.

각각의 새 페이지가 완전히 새로운 쿼리로 처리되기 때문에 UI가 success및 loading상태로 이동 합니다.

React Query에는 이 문제를 해결할 수 있는 멋진 기능 keepPreviousData이 있습니다.

Better Paginated Queries with keepPreviousData

pageIndex(또는 커서)를 증가시키기를 원하는 다음 예를 생각해 보십시오.

useQuery를 사용하더라도 기술적으로 여전히 잘 작동하지만
각 페이지나 커서에 대해 서로 다른 쿼리가 생성 및 삭제됨에 따라
UI가 성공 및 로드 상태에서 점프하거나 점프하지 못할 수 있습니다.

keepPreviousData를 true로 설정하면 다음과 같은 몇 가지 새로운 이점을 얻을 수 있습니다.

  • 쿼리 키가 변경되었더라도 새 데이터가 요청되는 동안 마지막으로 성공적으로 가져온 데이터를 사용할 수 있습니다.

  • 새 데이터가 도착하면 이전 데이터가 원활하게 스왑되어 새 데이터를 표시합니다.

  • isPreviousData는 쿼리가 현재 어떤 데이터를 제공하는지 알 수 있습니다.

function Todos() {
   const [page, setPage] = React.useState(0)
 
   const fetchProjects = (page = 0) => fetch('/api/projects?page=' + page).then((res) => res.json())
 
   const {
     isLoading,
     isError,
     error,
     data,
     isFetching,
     isPreviousData,
   } = useQuery(['projects', page], () => fetchProjects(page), { keepPreviousData : true })
 
   return (
     <div>
       {isLoading ? (
         <div>Loading...</div>
       ) : isError ? (
         <div>Error: {error.message}</div>
       ) : (
         <div>
           {data.projects.map(project => (
             <p key={project.id}>{project.name}</p>
           ))}
         </div>
       )}
       <span>Current Page: {page + 1}</span>
       <button
         onClick={() => setPage(old => Math.max(old - 1, 0))}
         disabled={page === 0}
       >
         Previous Page
       </button>{' '}
       <button
         onClick={() => {
           if (!isPreviousData && data.hasMore) {
             setPage(old => old + 1)
           }
         }}
         // Disable the Next Page button until we know a next page is available
         disabled={isPreviousData || !data?.hasMore}
       >
         Next Page
       </button>
       {isFetching ? <span> Loading...</span> : null}{' '}
     </div>
   )
 }

Infinite Queries

기존 데이터 집합에 추가 "더 많은" 데이터를 로드하거나
"무한 스크롤"할 수 있는 렌더링 목록도 매우 일반적인 UI 패턴입니다.

반응 쿼리는 useInfinite라는 유용한 버전의 useQuery를 지원합니다.이러한 유형의 목록을 쿼리하기 위한 쿼리입니다.

useInfiniteQuery를 사용할 시, 몇 가지가 다르다는 것을 알 수 있습니다.

  • data 이제 무한 쿼리 데이터를 포함하는 개체입니다.

  • data.pages 가져온 페이지를 포함하는 배열

  • data.pageParams 페이지를 가져오는 데 사용되는 페이지 매개변수를 포함하는 배열

  • fetchNextPage및 fetchPreviousPage기능을 사용할 수 있습니다

  • 로드할 데이터가 더 있는지와 가져올 정보가 있는지 확인 하는 데 getNextPageParam및 getPreviousPageParam옵션을 모두 사용할 수 있습니다. 이 정보는 쿼리 함수에서 추가 매개변수로 제공됩니다( fetchNextPage또는 fetchPreviousPage함수를 호출할 때 선택적으로 재정의될 수 있음 ).

  • getNextPageParam 이 undefined 가 아닌 다른 값을 반환할 때,
    hasNextPage 는 true 입니다. (hasNextPage 는 boolean 타입)

  • getPreviousPageParam 이 undefined 가 아닌 다른 값을 반환할 때,
    hasPreviousPage 는 true 입니다. (hasPreviousPage 는 boolean 타입)

  • isFetchingNextPage와 isFetchingPreviousPage 가 사용가능하며 (boolean) background refresh state 와 loading more state 를
    구분짓는데 사용됩니다.

참고: 쿼리에서 initialData또는 select 같은 옵션을 사용해서
데이터를 재구성할 때 여전히 data.pages하고 data.pageParam 속성이
포함되어있는지 확인하십시오. 그렇지 않으면 반환된 쿼리가 변경 사항을 덮어씁니다

예시)
한 번 에 3개의 페이지를 반환하는 API가 있다고 가정해 보겠습니다

fetch('/api/projects?cursor=0')
 // { data: [...], nextCursor: 3}
 fetch('/api/projects?cursor=3')
 // { data: [...], nextCursor: 6}
 fetch('/api/projects?cursor=6')
 // { data: [...], nextCursor: 9}
 fetch('/api/projects?cursor=9')
 // { data: [...] }

이 정보를 사용하여 우리는 "Load More" UI 를 만들 수 있습니다.

  • useInfiniteQuery : 기본적으로 첫 번째 데이터 그룹을 요청할 때까지 대기 중

  • 다음 쿼리에 대한 정보 반환 getNextPageParam

  • 호출 fetchNextPage기능

참고: 함수 에서 반환된 데이터 fetchNextPage를 재정의하지 않으려면 인수를 사용하여 호출하지 않는 것이 매우 중요합니다 .

예를 들어 다음 과 같이 하지 마십시오 .
onClick 이벤트가 함수로 전송됩니다.

<button onClick={fetchNextPage} />
import { useInfiniteQuery } from 'react-query'
 
 function Projects() {
   const fetchProjects = ({ pageParam = 0 }) =>
     fetch('/api/projects?cursor=' + pageParam)
 
   const {
     data,
     error,
     fetchNextPage,
     hasNextPage,
     isFetching,
     isFetchingNextPage,
     status,
   } = useInfiniteQuery('projects', fetchProjects, {
     getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
   })
 
   return status === 'loading' ? (
     <p>Loading...</p>
   ) : status === 'error' ? (
     <p>Error: {error.message}</p>
   ) : (
     <>
       {data.pages.map((group, i) => (
         <React.Fragment key={i}>
           {group.projects.map(project => (
             <p key={project.id}>{project.name}</p>
           ))}
         </React.Fragment>
       ))}
       <div>
         <button
           onClick={() => fetchNextPage()}
           disabled={!hasNextPage || isFetchingNextPage}
         >
           {isFetchingNextPage
             ? 'Loading more...'
             : hasNextPage
             ? 'Load More'
             : 'Nothing more to load'}
         </button>
       </div>
       <div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
     </>
   )
 }

무한 쿼리를 다시 가져와야 하는 경우 어떻게 됩니까?

무한 쿼리가 오래되어 다시 설정해야 하는 경우 첫 번째 쿼리에서 시작하여 각 그룹을 순차적으로 가져옵니다.
따라서 기본 데이터가 mutate되더라도 오래된 커서를 사용하지 않고 잠재적으로 중복되거나 레코드를 건너뛸 수 있습니다.
무한 쿼리 결과가 queryCache에서 제거되면 초기 상태에서 페이지 지정이 다시 시작되고 초기 그룹만 요청됩니다.

다시 가져오기 페이지

모든 페이지의 하위 집합만 재페치하려면 refetchPage 함수를 전달하여 useInfiniteQuery에서 반환된 재페치할 수 있습니다.

const { refetch } = useInfiniteQuery('projects', fetchProjects, {
   getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
 })
 
 // only refetch the first page
 refetch({ refetchPage: (page, index) => index === 0 })

이 함수를 두 번째 인수( queryFilters) 의 일부로 queryClient.refetchQueries , queryClient.invalidateQueries 또는 queryClient.resetQueries에 전달할 수도 있습니다.

  • refetchPage: (page: TData, index: number, allPages: TData[]) => boolean

이 함수는 각 페이지에 대해 실행되며 이 함수가 반환하는 페이지만 true다시 가져 옵니다

What if I need to pass custom information to my query function?

기본적으로 getNextPageParam에서 반환된 변수는 쿼리 함수에 제공되지만 경우에 따라 이를 재정의할 수도 있습니다. 다음과 같이 기본 변수를 재정의하는 fetchNextPage 함수에 사용자 지정 변수를 전달할 수 있습니다.

function Projects() {
   const fetchProjects = ({ pageParam = 0 }) =>
     fetch('/api/projects?cursor=' + pageParam)
 
   const {
     status,
     data,
     isFetching,
     isFetchingNextPage,
     fetchNextPage,
     hasNextPage,
   } = useInfiniteQuery('projects', fetchProjects, {
     getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
   })
 
   // Pass your own page param
   const skipToCursor50 = () => fetchNextPage({ pageParam: 50 })
 }

양방향 무한 목록을 구현하려면 어떻게 해야 합니까?

useInfiniteQuery('projects', fetchProjects, {
   getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
   getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
 })

페이지를 역순으로 표시하려면 어떻게 합니까?

useInfiniteQuery('projects', fetchProjects, {
   select: data => ({
     pages: [...data.pages].reverse(),
     pageParams: [...data.pageParams].reverse(),
   }),
 })

무한 쿼리를 수동으로 업데이트하려면 어떻게 합니까?

첫 번째 페이지를 수동으로 제거:

 queryClient.setQueryData('projects', data => ({
   pages: data.pages.slice(1),
   pageParams: data.pageParams.slice(1),
 }))

개별 페이지에서 단일 값을 수동으로 제거:

const newPagesArray = oldPagesArray?.pages.map((page) =>
   page.filter((val) => val.id !== updatedId)
 ) ?? []
 
 queryClient.setQueryData('projects', data => ({
   pages: newPagesArray,
   pageParams: data.pageParams,
 }))

페이지와 pageParams의 데이터 구조를 동일하게 유지해야 합니다!

Placeholder Query Data

placeholder data 란 무엇인가요?

Placeholder data 를 사용하면 initialData옵션 과 유사하게 쿼리에 이미 데이터가 있는 것처럼 작동할 수 있지만 데이터는 캐시에 유지되지 않습니다 .

이는 실제 데이터를 백그라운드에서 가져오는 동안 쿼리를 성공적으로 렌더링하기에 충분한 부분(또는 가짜) 데이터가 있는 상황에 유용합니다.

예: 개별 블로그 게시물 쿼리는 제목과 게시물 본문의 작은 스니펫만 포함하는 블로그 게시물의 상위 목록에서 "미리보기" 데이터를 가져올 수 있습니다. 이 부분 데이터를 개별 쿼리의 쿼리 결과에 유지하고 싶지는 않지만 전체 개체를 가져오기 위해 실제 쿼리가 완료되는 동안 가능한 빨리 콘텐츠 레이아웃을 표시하는 데 유용합니다.

쿼리에 대한 Placeholder data가 필요하기 전에 캐시에 제공하는 몇 가지 방법이 있습니다.

선언적으로
placeholderData비어 있는 경우 캐시를 미리 채우도록 쿼리에 제공

필연적으로
queryClient및 placeholderData옵션을 사용하여 데이터를 미리 가져오거나 가져옵니다.

값으로서의 placeholder data

function Todos() {
   const result = useQuery('todos', () => fetch('/todos'), {
     placeholderData: placeholderTodos,
   })
 }

함수로서의 placeholderData

쿼리의 placeholderData에 액세스하는 프로세스가 집중적이거나 모든 렌더링에서 수행하려는 작업이 아닌 경우 값을 메모하거나 메모이제된 함수를 placeholderData값 으로 전달할 수 있습니다 .

function Todos() {
   const placeholderData = useMemo(() => generateFakeTodos(), [])
   const result = useQuery('todos', () => fetch('/todos'), { placeholderData })
 }

캐시의 placeholderData

경우에 따라 다른 쿼리의 캐시된 결과에서 쿼리에 대한 placeholderData를 제공할 수 있습니다.

이에 대한 좋은 예는 게시물의 미리보기 버전에 대한 블로그 게시물 목록 쿼리에서 캐시된 데이터를 검색한 다음 개별 게시물 쿼리에 대한 placeholderData로 사용하는 것입니다.

function Todo({ blogPostId }) {
   const result = useQuery(['blogPost', blogPostId], () => fetch(`/blogPosts/${blogPostId}`), {
     placeholderData: () => {      
       return queryClient
         .getQueryData('blogPosts')
         ?.find(d => d.id === blogPostId)
     },
   })
 }

초기 쿼리 데이터 (Initial Query Data)

initialData를 사용하여 쿼리를 미리 채우기

앱에서 사용할 수 있는 쿼리에 대한 초기 데이터가 이미 있고 단순히 쿼리에 직접 제공할 수 있는 경우가 있습니다. 이 경우 config.initialData옵션을 사용하여 쿼리의 초기 데이터를 설정하고 초기 로드 상태를 건너뛸 수 있습니다!

중요 : initialData캐시에 유지되므로
부분적 또는 불완전한 데이터를 제공하고자한다면 placeholderData를 사용해야합니다.

function Todos() {
   const result = useQuery('todos', () => fetch('/todos'), {
     initialData: initialTodos,
   })
 }

staleTime 그리고 initialDataUpdatedAt

initialData 은 그저 새롭게 패치된 것처럼 처리됩니다.

이것은 또한 그것이 staleTime옵션에 의해 해석되는 방식에 영향을 미친다는 것을 의미합니다 .

  • initialData 을 설정하고 staleTime 를 설정하지 않거나, staleTime: 0 을 설정하면 쿼리가 마운트될 때 즉시 다시 가져옵니다.
function Todos() {  
   const result = useQuery('todos', () => fetch('/todos'), {
     initialData: initialTodos,
   })
 }
  • staleTime 의 시간을 설정할 수 있습니다.
    staleTime 을 1000으로 설정하면 그 시간동안 신선한 것으로 간주합니다.
function Todos() {
  
   const result = useQuery('todos', () => fetch('/todos'), {
     initialData: initialTodos,
     staleTime: 1000,
   })
 }

초기 데이터가 완전히 새로워지지 않으면 어떻게 될까요?

function Todos() {
  
   const result = useQuery('todos', () => fetch('/todos'), {
     initialData: initialTodos,
     staleTime: 60 * 1000 
     initialDataUpdatedAt: initialTodosUpdatedTimestamp 
   })
 }

위의 예에서 데이터는 1분 이내에 최신 상태여야 하며 initialData가 마지막으로 업데이트되었을 때 쿼리에 힌트를 줄 수 있으므로 쿼리가 데이터를 다시 가져와야 하는지 여부를 스스로 결정할 수 있습니다.

데이터를 미리 설정된 데이터로 처리하려면 prefetchQuery 또는 fetchQuery API를 사용하여 캐시를 미리 채우는 것이 좋습니다.

Initial Data Function

이 함수는 쿼리가 초기화될 때 한 번만 실행되어 소중한 메모리 및/또는 CPU를 절약합니다.

function Todos() {
   const result = useQuery('todos', () => fetch('/todos'), {
     initialData: () => {
       return getExpensiveTodos()
     },
   })
 }

캐시의 초기 데이터

경우에 따라 다른 쿼리의 캐시된 결과에서 쿼리에 대한 초기 데이터를 제공할 수 있습니다.

이에 대한 좋은 예는 개별 할일 항목에 대한 할일 목록 쿼리에서 캐시된 데이터를 검색한 다음 개별 할일 쿼리에 대한 초기 데이터로 사용하는 것입니다.

function Todo({ todoId }) {
   const result = useQuery(['todo', todoId], () => fetch('/todos'), {
     initialData: () => {      
       return queryClient.getQueryData('todos')?.find(d => d.id === todoId)
     },
   })
 }

캐시의 초기 데이터 initialDataUpdatedAt

초기 데이터에 관계없이 쿼리를 다시 가져와야 하는지 여부와 시기를 결정하는 데 필요한 모든 정보를 쿼리 인스턴스에 제공합니다.

 function Todo({ todoId }) {
   const result = useQuery(['todo', todoId], () => fetch(`/todos/${todoId}`), {
     initialData: () =>
       queryClient.getQueryData('todos')?.find(d => d.id === todoId),
     initialDataUpdatedAt: () =>
       queryClient.getQueryState('todos')?.dataUpdatedAt,
   })
 }

캐시의 조건부 초기 데이터

초기 데이터를 조회하는 데 사용하는 소스 쿼리가 오래된 경우 캐시된 데이터를 전혀 사용하지 않고 서버에서 가져오기만 하면 됩니다.

이 결정을 더 쉽게 하기 위해 queryClient.getQueryState대신 이 메서드를 state.dataUpdatedAt사용하여 쿼리가 필요에 따라 "

최신 "인지 결정하는 데 사용할 수 있는 타임스탬프를 포함하여 소스 쿼리에 대한 추가 정보를 얻을 수 있습니다.

function Todo({ todoId }) {
   const result = useQuery(['todo', todoId], () => fetch(`/todos/${todoId}`), {
     initialData: () => {
       const state = queryClient.getQueryState('todos')
 
       // If the query exists and has data that is no older than 10 seconds...
       if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {
         // return the individual todo
         return state.data.find(d => d.id === todoId)
       }
 
       // Otherwise, return undefined and let it fetch from a hard loading state!
     },
   })
 }

이 글도 읽어보자.
https://tkdodo.eu/blog/placeholder-and-initial-data-in-react-query

Prefetching

운이 좋다면 사용자가 데이터가 필요하기 전에 필요한 데이터를 미리 가져올 수 있도록 무엇을 할 것인지 충분히 알 수 있습니다! 이 경우 prefetchQuery메서드를 사용 하여 캐시에 넣을 쿼리 결과를 미리 가져올 수 있습니다.

const prefetchTodos = async () => {  
   await queryClient.prefetchQuery('todos', fetchTodos)
 }
  • 이 쿼리에 대한 데이터가 이미 캐시에 있고 무효화되지 않은 경우 데이터를 가져오지 않습니다.

  • staleTime예를 들어 a 가 전달 되는 경우 . prefetchQuery('todos', fn, { staleTime: 5000 })데이터가 지정된 staleTime보다 오래된 경우 쿼리를 가져옵니다.

  • useQuery프리페치된 쿼리에 대한 인스턴스가 나타나지 않으면 에 지정된 시간 이후에 삭제되고 가비지 수집됩니다 cacheTime

수동으로 쿼리 시작

또는 쿼리에 대한 데이터를 이미 동기적으로 사용할 수 있는 경우 미리 가져올 필요가 없습니다. 당신은 바로 사용할 수있는 쿼리 고객의 setQueryData방법을 직접 추가하거나 키를 기준으로 쿼리의 캐시 된 결과를 업데이트 할 수 있습니다.

 queryClient.setQueryData('todos', todos)

Mutations

쿼리와 달리 돌연변이는 일반적으로 데이터를 생성/업데이트/삭제하거나 서버 부작용을 수행하는 데 사용됩니다. 이를 위해 React Query는 useMutation후크를 내보냅니다 .

다음은 서버에 새 할 일을 추가하는 변형의 예입니다.

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 는 주어진 순간에 다음 상태 중 하나만 있을 수 있습니다.

isIdle또는 status === 'idle'- mutation가 현재 유휴 상태이거나 새로/재설정된 상태입니다.
isLoading또는 status === 'loading'- mutation가 현재 실행 중입니다.
isError또는 status === 'error'- mutation에 오류가 발생했습니다.
isSuccess또는 status === 'success'- mutation가 성공했고 mutation데이터를 사용할 수 있습니다

이러한 기본 상태 외에도 돌연변이 상태에 따라 더 많은 정보를 사용할 수 있습니다.

error- mutation가 error상태인 경우 error속성을 통해 오류가 발생 합니다.
data- mutation가 success상태에 있는 경우 data속성을 통해 데이터를 사용할 수 있습니다.

위의 예에서는 mutate에서 단일 변수 또는 객체를 사용하여 함수를 호출하여 돌연변이 함수에 변수를 전달할 수도 있음을 확인했습니다 .
심지어 단지 변수여도 괜찮습니다.

onSuccess옵션의 invalidateQueries과 setQueryData은
mutation의 매우 강력한 도구 입니다.

중요: 이 mutate함수는 비동기식 함수이므로 React 16 및 이전 버전 의 이벤트 콜백에서 직접 사용할 수 없습니다 .

이벤트에 액세스해야 하는 경우 다른 함수 onSubmit를 래핑해야 mutate합니다. 이것은 React 이벤트 풀링 때문 입니다.

 // React 16 및 이전 버전에서는 작동하지 않습니다.
 const CreateTodo = () => {
   const mutation = useMutation(event => {
     event.preventDefault()
     return fetch('/api', new FormData(event.target))
   })
 
   return <form onSubmit={mutation.mutate}>...</form>
 }
 
 // 이것은 작동합니다
 const CreateTodo = () => {
   const mutation = useMutation(formData => {
     return fetch('/api', formData)
   })
   const onSubmit = event => {
     event.preventDefault()
     mutation.mutate(new FormData(event.target))
   }
 
   return <form onSubmit={onSubmit}>...</form>
 }

mutation 상태 재설정

error또는 data돌연변이 요청 을 지워야 하는 경우가 있습니다 .
reset함수를 사용하여 이를 처리 할 수 있습니다 .

 const CreateTodo = () => {
   const [title, setTitle] = useState('')
   const mutation = useMutation(createTodo)
 
   const onCreateTodo = e => {
     e.preventDefault()
     mutation.mutate({ title })
   }
 
   return (
     <form onSubmit={onCreateTodo}>
       {mutation.error && (
         <h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
       )}
       <input
         type="text"
         value={title}
         onChange={e => setTitle(e.target.value)}
       />
       <br />
       <button type="submit">Create Todo</button>
     </form>
   )
 }

Mutation Side Effects

useMutation(addTodo, {
   onMutate: variables => {
     // // Mutation가 일어나려고 합니다!
 
     // 선택적으로 롤백과 같은 경우에 사용할 데이터가 포함된 컨텍스트를 반환합니다.
     return { id: 1 }
   },
   onError: (error, variables, context) => {
      // 오류가 발생했습니다!
     console.log(`rolling back optimistic update with id ${context.id}`)
   },
   onSuccess: (data, variables, context) => {
     // 붐 베이비!
   },
   onSettled: (data, error, variables, context) => {
     // 오류 또는 성공... 중요하지 않습니다!
   },
 })

콜백 함수에서 promise를 반환할 때 다음 콜백이 호출되기 전에 먼저 기다립니다.

useMutation(addTodo, {
   onSuccess: async () => {
     console.log("I'm first!")
   },
   onSettled: async () => {
     console.log("I'm second!")
   },
 })

Mutation 호출 시 useMutation에 정의된 콜백보다 더 많은 콜백을 트리거할 수 있습니다.

이를 통해 구성 요소별 side effect을 트리거할 수 있습니다.

이를 위해 Mutation 변수 이후 Mutation 함수에 동일한 콜백 옵션을 제공할 수 있습니다.

원되는 재정의에는 onSuccess, onError 및 onSettled가 포함됩니다.

이러한 추가 콜백은 돌연변이가 완료되기 전에 구성 요소가 마운트 해제되면 실행되지 않습니다.

useMutation(addTodo, {
   onSuccess: (data, variables, context) => {
     // I will fire first
   },
   onError: (error, variables, context) => {
     // I will fire first
   },
   onSettled: (data, error, variables, context) => {
     // I will fire first
   },
 })
 
 mutate(todo, {
   onSuccess: (data, variables, context) => {
     // I will fire second!
   },
   onError: (error, variables, context) => {
     // I will fire second!
   },
   onSettled: (data, error, variables, context) => {
     // I will fire second!
   },
 })

Promises

성공하거나 오류를 발생시키는 약속을 얻으려면 돌연변이 대신 돌연변이 Async를 사용합니다. 예를 들어, 이것은 side effects을 구성하는 데 사용될 수 있습니다.

const mutation = useMutation(addTodo)
 
 try {
   const todo = await mutation.mutateAsync(todo)
   console.log(todo)
 } catch (error) {
   console.error(error)
 } finally {
   console.log('done')
 }

Retry

기본적으로 오류 시 mutation를 재시도하지 않지만 재시도 옵션을 사용하면 가능합니다.

const mutation = useMutation(addTodo, {
   retry: 3,
 })

디바이스가 오프라인 상태이기 때문에 mutation가 실패하면 디바이스가 다시 연결될 때 동일한 순서로 다시 시도합니다.

mutation 유지

mutation는 필요한 경우 저장소에 유지되고 나중에 다시 시작할 수 있습니다. 이것은 hydration functions으로 수행할 수 있습니다.

const queryClient = new QueryClient()
 
 // "addTo" 돌연변이 정의
 queryClient.setMutationDefaults('addTodo', {
   mutationFn: addTodo,
   onMutate: async (variables) => {
     // 할 일 목록에 대한 현재 쿼리 취소
     await queryClient.cancelQueries('todos')
 
     // 낙관적 할일 생성
     const optimisticTodo = { id: uuid(), title: variables.title }
 
    // 할 일 목록에 낙관적 할 일 추가
     queryClient.setQueryData('todos', old => [...old, optimisticTodo])
 
     // 낙관적 할일과 함께 컨텍스트를 반환합니다.
     return { optimisticTodo }
   },
   onSuccess: (result, variables, context) => {
     // 할 일 목록의 낙관적 할 일을 결과로 바꿉니다.
     queryClient.setQueryData('todos', old => old.map(todo => todo.id === context.optimisticTodo.id ? result : todo))
   },
   onError: (error, variables, context) => {
    // 할일 목록에서 낙관적 할일 제거
     queryClient.setQueryData('todos', old => old.filter(todo => todo.id !== context.optimisticTodo.id))
   },
   retry: 3,
 })
 
 // 일부 구성 요소에서 돌연변이 시작:
 const mutation = useMutation('addTodo')
 mutation.mutate({ title: 'title' })
 
 // 예를 들어 장치가 오프라인이기 때문에 mutation가 일시 중지된 경우
 // 그러면 일시 중지된 돌연변이는 애플리케이션이 종료될 때 dehydrate될 수 있습니다.
 const state = dehydrate(queryClient)
 
 // 그런 다음 응용 프로그램이 시작될 때 mutation를 다시 수화할 수 있습니다.
 hydrate(queryClient, state)
 
 // 일시 중지된 mutation를 재개합니다.
 queryClient.resumePausedMutations()

쿼리 무효화

쿼리를 다시 가져오기 전에 쿼리가 오래될 때까지 기다리는 것이 항상 작동하는 것은 아닙니다.

특히 사용자가 수행한 작업으로 인해 쿼리 데이터가 오래되었다는 사실을 알고 있는 경우에는 더욱 그렇습니다.

이를 위해 쿼리를 오래된 것으로 지능적으로 표시하고 잠재적으로 다시 가져올 수 QueryClient있는 invalidateQueries방법이 있습니다

// 캐시의 모든 쿼리를 무효화합니다.
 queryClient.invalidateQueries()
// `todos`로 시작하는 키로 모든 쿼리를 무효화합니다.
 queryClient.invalidateQueries('todos')

참고: 정규화된 캐시를 사용하는 다른 라이브러리가 명령적으로 또는 스키마 추론을 통해 새 데이터로 로컬 쿼리를 업데이트하려고 시도하는 반면,

React Query는 정규화된 캐시를 유지 관리하는 데 수반되는 수동 작업을 피하고 대신 대상 무효화,
백그라운드 를 규정하는 도구를 제공합니다.

-refetching 및 궁극적으로 원자 업데이트 .

invalidateQueries 로 쿼리가 무효화 되면 두 가지 일이 발생합니다.

오래된 것으로 표시됩니다. staleTime에서 사용 중인 모든 구성을 재정의합니다.

useQuery또는 관련 후크 를 통해 렌더링 되는 경우 백그라운드에서도 다시 가져옵니다

쿼리 일치

invalidateQueries및 removeQueries(및 부분 쿼리 일치를 지원하는 기타) 와 같은 API를 사용할 때 접두사로 여러 쿼리를 일치시키거나 매우 구체적이고 정확한 쿼리를 일치시킬 수 있습니다. 사용할 수 있는 필터 유형에 대한 정보는 쿼리 필터 를 참조하십시오 .

이 예에서는 todos접두사를 사용하여 todos쿼리 키에서 시작하는 모든 쿼리를 무효화 할 수 있습니다 .

import { useQuery, useQueryClient } from 'react-query'
 
 // Get QueryClient from the context
 const queryClient = useQueryClient()
 
 queryClient.invalidateQueries('todos')
 
 // Both queries below will be invalidated
 const todoListQuery = useQuery('todos', fetchTodoList)
 const todoListQuery = useQuery(['todos', { page: 1 }], fetchTodoList)

invalidateQueries 메서드에 보다 구체적인 쿼리 키를 전달하여 특정 변수가 있는 쿼리를 무효화할 수도 있습니다.

queryClient.invalidateQueries(['todos', { type: 'done' }])
 
 // 아래 쿼리는 무효화됩니다.
 const todoListQuery = useQuery(['todos', { type: 'done' }], fetchTodoList)
 
 // 그러나 아래 쿼리는 무효화되지 않습니다.
 const todoListQuery = useQuery('todos', fetchTodoList)

invalidateQueries 는 매우 유연하지만, exact 옵션을 통해
완전히 일치한 것만 무효화 시킬 수 있습니다.

queryClient.invalidateQueries('todos', { exact: true })
 
 // 아래 쿼리는 무효화됩니다.
 const todoListQuery = useQuery(['todos'], fetchTodoList)
 
  // 그러나 아래 쿼리는 무효화되지 않습니다.
 const todoListQuery = useQuery(['todos', { type: 'done' }], fetchTodoList)

딩신은 다음과 같이 더 세분화해서 검색해 무효화 할 수 있습니다.

queryClient.invalidateQueries({
   predicate: query =>
     query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10,
 })
 
 // 아래 쿼리는 무효화됩니다.
 const todoListQuery = useQuery(['todos', { version: 20 }], fetchTodoList)
 
 // 아래 쿼리는 무효화됩니다.
 const todoListQuery = useQuery(['todos', { version: 10 }], fetchTodoList)
 
 // 그러나 아래 쿼리는 무효화되지 않습니다.
 const todoListQuery = useQuery(['todos', { version: 5 }], fetchTodoList)

mutation로 인한 무효화

쿼리 무효화는 전투의 절반에 불과합니다.
그것들을 무효화할 때 를 아는 것은 나머지 절반입니다.

일반적으로 앱의 변형이 성공하면 변형의 새로운 변경 사항을 설명하기 위해 무효화하고 다시 가져와야 하는 관련 쿼리가 애플리케이션에 있을 가능성이 매우 높습니다.

예를 들어, 새로운 할 일을 게시하는 mutation이 있다고 가정합니다.

const mutation = useMutation(postTodo)

성공적인 postTodo변형이 발생 하면 모든 todos쿼리가 무효화되고
새 할 일 항목을 표시하기 위해 다시 가져올 수 있습니다.

이렇게 하려면 useMutation의 onSuccess옵션과 client의 invalidateQueries기능을 사용할 수 있습니다 .

import { useMutation, useQueryClient } from 'react-query'
 
 const queryClient = useQueryClient()
 
  // 이 변형이 성공하면 `todos` 또는 `reminders` 쿼리 키로 모든 쿼리를 무효화합니다.
 const mutation = useMutation(addTodo, {
   onSuccess: () => {
     queryClient.invalidateQueries('todos')
     queryClient.invalidateQueries('reminders')
   },
 })

mutation 반응의 업데이트

서버에서 개체 를 업데이트 하는 mutation를 처리할 때 mutation의 응답으로 새 개체가 자동으로 반환되는 것이 일반적입니다.

해당 항목에 대한 쿼리를 다시 가져오고 이미 가지고 있는 데이터에 대한 네트워크 호출을 낭비하는 대신 돌연변이 함수에서 반환된 객체를 활용하고 쿼리 클라이언트의setQueryData 메서드를 사용하여 즉시 새 데이터로 기존 쿼리를 업데이트할 수 있습니다 .

 const queryClient = useQueryClient()
 
 const mutation = useMutation(editTodo, {
   onSuccess: data => {
     queryClient.setQueryData(['todo', { id: 5 }], data)
   }
 })
 
 mutation.mutate({
   id: 5,
   name: 'Do the laundry',
 })
 
 /// 아래 쿼리는 다음의 응답으로 업데이트됩니다.
 // 성공적인 돌연변이
 const { status, data, error } = useQuery(['todo', { id: 5 }], fetchTodoById)

onSuccess로직을 재사용 가능한 변형 에 묶고 싶을 수도 있습니다. 이를 위해 다음과 같은 사용자 정의 후크를 만들 수 있습니다.

const useMutateTodo = () => {
   const queryClient = useQueryClient()
 
   return useMutation(editTodo, {
    // 두 번째 인수는 `mutate` 함수가 받는 변수 객체입니다.
     onSuccess: (data, variables) => {
       queryClient.setQueryData(['todo', { id: variables.id }], data)
     },
   })
 }

낙관적 업데이트

mutation를 수행하기 전에 상태를 낙관적으로 업데이트하면 mutation가 실패할 확률이 0이 아닙니다.

대부분의 경우 낙관적 쿼리에 대해 다시 가져오기를 트리거하여 실제 서버 상태로 되돌릴 수 있습니다.

그러나 일부 상황에서는 다시 가져오기가 올바르게 작동하지 않을 수 있으며 mutation 오류는 다시 가져오기를 불가능하게 하는 일종의 서버 문제를 나타낼 수 있습니다.

이 경우 대신 업데이트를 롤백하도록 선택할 수 있습니다.

이를 위해 useMutation의 onMutate처리기 옵션을 사용하면 나중에 마지막 인수로 onError및 onSettled처리기 모두에 전달될 값을 반환할 수 있습니다 . 대부분의 경우 롤백 기능을 전달하는 것이 가장 유용합니다.

새 할 일 추가 시 할 일 목록 업데이트

const queryClient = useQueryClient()
 
 useMutation(updateTodo, {
    // mutate가 호출될 때:
   onMutate: async newTodo => {
    // 나가는 리페치를 취소합니다(그래서 낙관적인 업데이트를 덮어쓰지 않도록).
     await queryClient.cancelQueries('todos')
 
      // 이전 값 스냅샷
     const previousTodos = queryClient.getQueryData('todos')
 
     // 새 값으로 낙관적으로 업데이트
     queryClient.setQueryData('todos', old => [...old, newTodo])
 
      // 스냅샷 값이 있는 컨텍스트 객체를 반환합니다.
     return { previousTodos }
   },
   // 돌연변이가 실패하면 onMutate에서 반환된 컨텍스트를 사용하여 롤백합니다.
   onError: (err, newTodo, context) => {
     queryClient.setQueryData('todos', context.previousTodos)
   },
  // 오류 또는 성공 후 항상 다시 가져옵니다.
   onSettled: () => {
     queryClient.invalidateQueries('todos')
   },
 })

단일 할일 업데이트

useMutation(updateTodo, {
   // mutate가 호출될 때:
   onMutate: async newTodo => {
     // 나가는 리페치를 취소합니다(그래서 낙관적인 업데이트를 덮어쓰지 않도록).
     await queryClient.cancelQueries(['todos', newTodo.id])
 
     // 이전 값 스냅샷
     const previousTodo = queryClient.getQueryData(['todos', newTodo.id])
 
      // 새 값으로 낙관적으로 업데이트
     queryClient.setQueryData(['todos', newTodo.id], newTodo)
 
      // 이전 작업과 새 작업이 있는 컨텍스트를 반환합니다.
     return { previousTodo, newTodo }
   },
    // 돌연변이가 실패하면 위에서 반환한 컨텍스트를 사용합니다.
   onError: (err, newTodo, context) => {
     queryClient.setQueryData(
       ['todos', context.newTodo.id],
       context.previousTodo
     )
   },
   // 오류 또는 성공 후 항상 다시 가져옵니다.
   onSettled: newTodo => {
     queryClient.invalidateQueries(['todos', newTodo.id])
   },
 })

원하는 경우 onSettled분리 onError및 onSuccess처리기 대신 함수를 사용할 수도 있습니다 .

useMutation(updateTodo, {
   // ...
   onSettled: (newTodo, error, variables, context) => {
     if (error) {
       // do something
     }
   },
 })

쿼리 취소

기본적으로 promises 이 해결되기 전에 마운트 해제되거나 사용되지 않는 쿼리는 취소되는 대신 무시됩니다.

  • 대부분의 응용 프로그램에서는 오래된 쿼리를 무시하는 것으로 충분합니다.
  • 일부 쿼리 기능에는 취소 API가 제공되지 않을 수 있습니다.
  • 취소 API를 사용할 수 있는 경우 일반적으로 유틸리티/라이브러리 간에 구현이 다릅니다(예: Fetch vs Axios vs XMLHttpRequest).

하지만 걱정하지 마세요! 쿼리가 고대역폭이거나 다운로드 비용이 매우 높을 경우 React Query는 취소 토큰 또는 기타 관련 API를 사용하여 쿼리 요청 을 취소 하는 일반적인 방법을 제공합니다 . 이 기능과 통합하려면 cancel요청 취소를 구현하는 쿼리에서 반환된 약속에 함수를 연결 하십시오. 쿼리가 오래되거나 비활성화되면 이 promise.cancel함수가 호출됩니다(사용 가능한 경우)

import axios from 'axios'
 
 const query = useQuery('todos', () => {
   // 이 요청에 대한 새 CancelToken 소스를 만듭니다.
   const CancelToken = axios.CancelToken
   const source = CancelToken.source()
 
   const promise = axios.get('/todos', {
      // 소스 토큰을 요청에 전달
     cancelToken: source.token,
   })
 
  // React Query가 `promise.cancel` 메소드를 호출하면 요청을 취소합니다.
   promise.cancel = () => {
     source.cancel('Query was cancelled by React Query')
   }
 
   return promise
 })

Using

const query = useQuery('todos', () => {
    // 이 요청에 대한 새 AbortController 인스턴스를 만듭니다.
   const controller = new AbortController()
   // abortController의 신호를 가져옵니다.
   const signal = controller.signal
 
   const promise = fetch('/todos', {
     method: 'get',
      // 요청에 신호 전달
     signal,
   })
 
   // React Query가 `promise.cancel` 메소드를 호출하면 요청을 취소합니다.
   promise.cancel = () => controller.abort()
 
   return promise
 })

수동 취소

쿼리를 수동으로 취소할 수 있습니다. 예를 들어 요청을 완료하는 데 시간이 오래 걸리는 경우 사용자가 취소 버튼을 클릭하여 요청을 중지하도록 할 수 있습니다. 이렇게 하려면 를 호출하기만 하면 됩니다 queryClient.cancelQueries(key). 경우 promise.cancel사용할 수 있으며, 쿼리 요청을 취소한다 반응한다.

const [queryKey] = useState('todos')
 
 const query = useQuery(queryKey, () => {
   const controller = new AbortController()
   const signal = controller.signal
 
   const promise = fetch('/todos', {
     method: 'get',
     signal,
   })
 
   // Cancel the request if React Query calls the `promise.cancel` method
   promise.cancel = () => controller.abort()
 
   return promise
 })
 
 const queryClient = useQueryClient();
 
 return (
   <button onClick={(e) => {
     e.preventDefault();
     queryClient.cancelQueries(queryKey);
    }}>Cancel</button>
 )

스크롤 복원

일반적으로 웹 브라우저에서 이전에 방문한 페이지로 이동하면 해당 페이지에서 다른 페이지로 이동하기 전의 정확한 위치로 페이지가 스크롤됩니다. 이것을 스크롤 복원 이라고 하며 웹 애플리케이션이 클라이언트 측 데이터 가져오기로 이동하기 시작한 이후로 약간 퇴행했습니다. 그러나 React Query를 사용하면 더 이상 그렇지 않습니다.

기본적으로 모든 쿼리(페이지 매김 및 무한 쿼리 포함)에 대한 "스크롤 복원"이 React Query에서 Just Works™️입니다. 그 이유는 쿼리 결과가 캐시되고 쿼리가 렌더링될 때 동기적으로 검색될 수 있기 때문입니다. 쿼리가 충분히 오래 캐시되고(기본 시간은 5분) 가비지 수집되지 않은 한 스크롤 복원은 항상 기본적으로 작동합니다.

필터

React Query 내의 일부 메소드는 QueryFilters또는 MutationFilters객체를 허용 합니다

Query Filters

Query Filters

// 모든 쿼리 취소
await queryClient.cancelQueries()

// 키에서 'posts'로 시작하는 모든 비활성 쿼리를 제거합니다.
queryClient.removeQueries('posts', { inactive: true })

// 모든 활성 쿼리를 다시 가져옵니다.
await queryClient.refetchQueries({ active: true })

// 키에서 `posts`로 시작하는 모든 활성 쿼리를 다시 가져옵니다. 
await queryClient.refetchQueries('posts', { active: true })

쿼리 필터 개체는 다음 속성을 지원합니다.

  • exact?: boolean

쿼리 키로 쿼리를 포괄적으로 검색하지 않으려면 전달한 exact: true정확한 쿼리 키가 있는 쿼리만 반환 하는 옵션을 전달할 수 있습니다 .

  • active?: boolean
    설정하면 true활성 쿼리와 일치합니다.
    설정하면 false비활성 쿼리와 일치합니다.

  • inactive?: boolean
    설정하면 true비활성 쿼리와 일치합니다.
    설정하면 false활성 쿼리와 일치합니다.

  • stale?: boolean
    설정하면 true오래된 쿼리와 일치합니다.
    설정하면 false새로운 쿼리와 일치합니다.

  • fetching?: boolean
    설정하면 true현재 가져오는 쿼리와 일치합니다.
    설정하면 false가져오지 않는 쿼리와 일치합니다.

  • predicate?: (query: Query) => boolean
    이 술어 함수는 캐시의 모든 단일 쿼리에 대해 호출되며 인 쿼리에 대해 truthy를 반환할 것으로 예상됩니다 found.

  • queryKey?: QueryKey
    일치시킬 쿼리 키를 정의하려면 이 속성을 설정하십시오.

Mutation Filters

돌연변이 필터는 돌연변이와 일치시킬 특정 조건이 있는 객체입니다.

// 가져오는 모든 돌연변이의 수를 가져옵니다.
 await queryClient.isMutating()
 
 // mutationKey로 돌연변이 필터링
  await queryClient.isMutating({ mutationKey: "post" })
 
 // 술어 함수를 사용하여 돌연변이 필터링
 await queryClient.isMutating({ predicate: (mutation) => mutation.options.variables?.id === 1 })

변형 필터 개체는 다음 속성을 지원합니다.

exact?: boolean
돌연변이 키로 돌연변이를 포괄적으로 검색하지 않으려면 전달한 exact: true정확한 돌연변이 키가 있는 돌연변이만 반환 하는 옵션을 전달할 수 있습니다 .

fetching?: boolean
설정하면 true현재 가져오는 돌연변이와 일치합니다.
설정하면 false가져오지 않는 돌연변이와 일치합니다.

predicate?: (mutation: Mutation) => boolean
이 술어 함수는 캐시의 모든 단일 돌연변이에 대해 호출되며 다음과 같은 돌연변이에 대해 참을 반환할 것으로 예상됩니다 found.

mutationKey?: MutationKey
일치시킬 돌연변이 키를 정의하려면 이 속성을 설정하십시오.

SSR

React Query는 서버에서 데이터를 미리 가져오고 이를 queryClient에 전달하는 두 가지 방법을 지원합니다

  • initialData : 데이터를 직접 미리 가져 와서 다음과 같이 전달하십시오.

간단한 케이스를 위한 빠른 설정
몇 가지 주의 사항이 있습니다.

--

  • 서버에서 쿼리를 미리 가져오고 캐시를 dehydrate한 다음 클라이언트에서 다시 rehydrate 합니다.

약간 더 많은 설정이 필요합니다.

Next.js 사용

이러한 메커니즘의 정확한 구현은 플랫폼마다 다를 수 있지만 2가지 형태의 사전 렌더링 을 지원하는 Next.js부터 시작하는 것이 좋습니다 .

  • 정적 생성(SSG)
  • 서버 측 렌더링(SSR)

React Query는 사용 중인 플랫폼에 관계없이 이러한 두 가지 형태의 사전 렌더링을 모두 지원합니다.

  • initialData 사용

Next.js의 getStaticProps 또는 getServerSideProps두 방법 중 하나로 가져온 데이터를 useQuery's' initialData옵션에 전달할 수 있습니다 .

React Query의 관점에서 이들은 동일한 방식으로 통합되며
getStaticProps 다음과 같습니다.

export async function getStaticProps() {
   const posts = await getPosts()
   return { props: { posts } }
 }
 
 function Posts(props) {
   const { data } = useQuery('posts', getPosts, { initialData: props.posts })
 
   // ...
 }

설정은 최소이며 경우에 따라 빠른 솔루션이 될 수 있지만 전체 접근 방식과 비교할 때 고려해야 할 몇 가지 절충점이 있습니다 .

useQuery트리에서 더 깊은 곳에서 구성 요소를 호출하는 경우 initialData해당 지점까지 아래로 전달해야 합니다.

useQuery여러 위치에서 동일한 쿼리로 호출 하는 경우 initialData모든 위치에 전달해야 합니다.

서버에서 쿼리를 가져온 시간을 알 수 있는 방법이 없으므로 쿼리를 다시 가져와야 dataUpdatedAt하는지 여부를 결정하는 것은 페이지가 로드된 시간을 기준으로 합니다.

Using Hydration

React Query는 Next.js의 서버에서 여러 쿼리를 미리 가져온 다음 해당 쿼리를 queryClient 로 dehydrating 하는 것을 지원합니다.

즉, 서버는 페이지 로드 시 즉시 사용할 수 있는 마크업을 미리 렌더링할 수 있으며 JS를 사용할 수 있게 되면 React Query는 라이브러리의 전체 기능으로 이러한 쿼리를 업그레이드하거나 hydrate 할 수 있습니다 .

여기에는 서버에서 렌더링된 이후로 오래된 쿼리가 클라이언트에서 다시 가져오는 것이 포함됩니다.

서버에서 쿼리 캐싱을 지원하고 hydration를 설정하려면:

  • 앱 내부와 인스턴스 ref(또는 React 상태)에서 새 QueryClient인스턴스를 만듭니다 . 이렇게 하면 구성 요소 수명 주기당 한 번만 QueryClient를 생성하면서 데이터가 서로 다른 사용자와 요청 간에 공유되지 않습니다.

  • 앱 구성 요소를 래핑 하고 클라이언트 인스턴스로 전달

  • 앱 구성 요소를 래핑 하고 다음에서 dehydratedState소품을 전달합니다.pageProps

// _app.jsx
 import { Hydrate, QueryClient, QueryClientProvider } from 'react-query'
 
 export default function MyApp({ Component, pageProps }) {
   const [queryClient] = React.useState(() => new QueryClient())
 
   return (
     <QueryClientProvider client={queryClient}>
       <Hydrate state={pageProps.dehydratedState}>
         <Component {...pageProps} />
       </Hydrate>
     </QueryClientProvider>
   )
 }

이제 getStaticProps(SSG의 경우) 또는 getServerSideProps(SSR의 경우) 페이지의 일부 데이터를 미리 가져올 준비가 되었습니다 . React Query의 관점에서 볼 때 동일한 방식으로 통합되는 getStaticProps것이 아래에 나와 있습니다.

  • 각 페이지 요청에 대해 새 QueryClient인스턴스 를 만듭니다 . 이렇게 하면 사용자와 요청 간에 데이터가 공유되지 않습니다.
  • 클라이언트 prefetchQuery방법을 사용하여 데이터를 미리 가져 오고 완료될 때까지 기다립니다.
  • dehydrate 사용 쿼리 캐시를 탈수와를 통해 페이지에 전달하는 dehydratedState소품. 이것은 캐시에서 선택되는 것과 동일한 소품입니다._
 // pages/posts.jsx
 import { dehydrate, QueryClient, useQuery } from 'react-query';
 
 export async function getStaticProps() {
   const queryClient = new QueryClient()
 
   await queryClient.prefetchQuery('posts', getPosts)
 
   return {
     props: {
       dehydratedState: dehydrate(queryClient),
     },
   }
 }
 
 function Posts() {
    // 이 useQuery는 더 깊은 자식에서 발생할 수도 있습니다.
   // "게시물" 페이지, 데이터는 어느 쪽이든 즉시 사용할 수 있습니다.
   const { data } = useQuery('posts', getPosts)
 
   // 이 쿼리는 서버에서 미리 가져오지 않았으며 시작되지 않습니다.
   // 클라이언트에서 두 패턴을 혼합해도 괜찮을 때까지 가져옵니다.
   const { data: otherData } = useQuery('posts-2', getPosts)
 
   // ...
 }

설명된 대로 일부 쿼리를 미리 가져오고 다른 쿼리는 queryClient에서 가져오도록 하는 것이 좋습니다. 즉 prefetchQuery, 특정 쿼리를 추가하거나 제거하여 콘텐츠 서버가 렌더링할지 여부를 제어할 수 있습니다.

다른 프레임워크 또는 사용자 정의 SSR 프레임워크 사용

On the Server

요청 처리기 내부에 새 QueryClient인스턴스를 만듭니다 .

이렇게 하면 다른 사용자와 요청 간에 데이터가 공유되지 않습니다.

클라이언트를 사용하여 필요한 모든 데이터를 미리 가져옵니다.

클라이언트 Dehydrate

클라이언트 provider와 dehydrated state를 사용 하여 앱을 렌더링합니다 . 이것은 매우 중요합니다! 클라이언트의 hydration 이 서버와 정확히 동일한 마크업을 생성하도록 하려면 동일한 dehydrated state 를 사용하여 서버와 클라이언트를 모두 렌더링해야 합니다.

HTML을 사용하여 클라이언트에 보낼 dehydrated 캐시를 직렬화하고 포함합니다.

보안 참고: 데이터를 직렬화 JSON.stringify하면 XSS 취약점의 위험에 노출될 수 있습니다. 이 블로그 게시물은 해결 방법과 이유를 설명합니다.

  import { dehydrate, Hydrate, QueryClient, QueryClientProvider } from 'react-query';
 
 function handleRequest (req, res) {
   const queryClient = new QueryClient()
   await queryClient.prefetchQuery('key', fn)
   const dehydratedState = dehydrate(queryClient)
 
   const html = ReactDOM.renderToString(
     <QueryClientProvider client={queryClient}>
       <Hydrate state={dehydratedState}>
         <App />
       </Hydrate>
     </QueryClientProvider>
   )
 
   res.send(`
     <html>
       <body>
         <div id="root">${html}</div>
         <script>
           window.__REACT_QUERY_STATE__ = ${JSON.stringify(dehydratedState)};
         </script>
       </body>
     </html>
   `)
 }

Client

  • HTML을 사용하여 클라이언트에 전송된 dehydrated cache state를 구문 분석합니다.
  • 새 QueryClient인스턴스 만들기
  • 클라이언트 공급자와 dehydrated state를 사용 하여 앱을 렌더링합니다 . 이것은 매우 중요합니다! 클라이언트의 hydration이 서버와 정확히 동일한 마크업을 생성하도록 하려면 동일한 dehydrated state를 사용하여 서버와 클라이언트를 모두 렌더링해야 합니다.
import { Hydrate, QueryClient, QueryClientProvider } from 'react-query'
 
 const dehydratedState = window.__REACT_QUERY_STATE__
 
 const queryClient = new QueryClient()
 
 ReactDOM.hydrate(
   <QueryClientProvider client={queryClient}>
     <Hydrate state={dehydratedState}>
       <App />
     </Hydrate>
   </QueryClientProvider>,
   document.getElementById('root')
 )

팁, 요령 및 주의 사항

성공적인 쿼리만 탈수에 포함됩니다.
오류가 있는 쿼리는 자동으로 dehydration에서 제외됩니다. 이는 기본 동작이 이러한 쿼리가 서버에 로드되지 않은 척하는 것이며 일반적으로 로드 상태를 대신 표시하고 queryClient에서 쿼리를 재시도하는 것임을 의미합니다. 이것은 오류와 상관없이 발생합니다.

때때로 이 동작은 바람직하지 않습니다. 특정 오류나 쿼리 대신 올바른 상태 코드로 오류 페이지를 렌더링하고 싶을 수도 있습니다. 이러한 경우 fetchQuery오류를 사용 하고 포착하여 수동으로 처리하십시오.

부실성은 서버에서 쿼리를 가져온 시점부터 측정됩니다.
쿼리는 기한에 따라 오래된 것으로 간주됩니다 dataUpdatedAt. 여기서 주의할 점은 이것이 제대로 작동하려면 서버에 정확한 시간이 있어야 하지만 UTC 시간이 사용되므로 시간대는 고려하지 않는다는 것입니다.

때문에 staleTime기본값이하는 0쿼리는 기본적으로 페이지로드에 백그라운드에서 다시 꺼집니다. staleTime특히 마크업을 캐시하지 않는 경우 이러한 이중 가져오기를 방지하기 위해 더 높은 값을 사용할 수 있습니다 .

이 오래된 쿼리를 다시 가져오는 것은 CDN에서 마크업을 캐싱할 때 완벽하게 일치합니다! 서버에서 페이지를 다시 렌더링하지 않아도 되도록 페이지 자체의 캐시 시간을 상당히 높게 설정할 수 있지만 staleTime사용자가 페이지를 방문하는 즉시 백그라운드에서 데이터를 다시 가져오도록 쿼리를 낮게 구성 할 수 있습니다. 일주일 동안 페이지를 캐시하고 싶지만 데이터가 하루보다 오래된 경우 페이지 로드 시 자동으로 데이터를 다시 가져오고 싶습니까?

캐싱 예제

기본 예

이 캐싱 예제는 다음의 스토리와 수명 주기를 보여줍니다.

  • 캐시 데이터가 있거나 없는 쿼리 인스턴스

  • 백그라운드 다시 가져오기

  • 비활성 쿼리

  • Garbage Collection

    기본 캐시시간인 5분을 사용한다고 가정해 봅시다.
    그리고 staleTime 은 0분을 기본으로 한다고 해봅시다.

useQuery('todos', fetchTodos)마운트 의 새 인스턴스입니다 .
이 쿼리 + 변수 조합으로 다른 쿼리가 만들어지지 않았기 때문에 이 쿼리는 하드 로드 상태를 표시하고 데이터를 가져오기 위해 네트워크 요청을 합니다.
그런 다음 'todos'및 fetchTodos해당 캐시에 대한 고유 식별자를 사용하여 데이터를 캐시합니다.
후크는 구성된 후 자신을 부실한 것으로 표시합니다 staleTime(기본값은 0, 또는 즉시).
useQuery('todos', fetchTodos)다른 곳 에서 마운트 의 두 번째 인스턴스입니다 .
이 정확한 데이터는 이 쿼리의 첫 번째 인스턴스에서 캐시에 있기 때문에 해당 데이터는 캐시에서 즉시 반환됩니다.
새 인스턴스가 화면에 나타나므로 두 쿼리 모두에 대해 백그라운드 다시 가져오기가 트리거됩니다(단 하나의 요청만).
가져오기가 성공하면 두 인스턴스 모두 새 데이터로 업데이트됩니다.
useQuery('todos', fetchTodos)쿼리 의 두 인스턴스가 모두 마운트 해제되어 더 이상 사용되지 않습니다.
이 쿼리의 활성 인스턴스가 더 이상 없기 때문에 쿼리 cacheTime를 삭제하고 가비지 수집하는 데 사용하여 캐시 시간 초과가 설정됩니다 (기본값은 5분 ).
캐시 시간 초과가 useQuery('todos', fetchTodos)마운트의 다른 인스턴스를 완료하기 전에 . fetchTodos새로운 값으로 쿼리를 채우기 위해 백그라운드에서 함수가 실행되는 동안 쿼리는 즉시 사용 가능한 캐시된 값을 반환합니다 .
useQuery('todos', fetchTodos)마운트 해제 의 마지막 인스턴스입니다 .
5분useQuery('todos', fetchTodos) 이내에 의 인스턴스가 더 이상 나타나지 않습니다 .
이 쿼리와 해당 데이터가 삭제되고 가비지 수집됩니다.

기본 쿼리 기능

어떤 이유로든 전체 앱에 대해 동일한 쿼리 기능을 공유하고 쿼리 키를 사용하여 가져올 항목을 식별할 수 있기를 원하는 경우 React Query에 기본 쿼리 기능 을 제공하여 이를 수행할 수 있습니다 .

// 쿼리 키를 받을 기본 쿼리 함수를 정의합니다.
 // 여기서 queryKey는 Array로 보장됩니다.
 const defaultQueryFn = async ({ queryKey }) => {
   const { data } = await axios.get(`https://jsonplaceholder.typicode.com${queryKey[0]}`);
   return data;
 };
 
 // defaultOptions를 사용하여 앱에 기본 쿼리 함수를 제공합니다.
 const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
       queryFn: defaultQueryFn,
     },
   },
 })
 
 function App() {
   return (
     <QueryClientProvider client={queryClient}>
       <YourApp />
     </QueryClientProvider>
   )
 }
 
  // 이제 키를 전달하기만 하면 됩니다!
 function Posts() {
   const { status, data, error, isFetching } = useQuery('/posts')
 
   // ...
 }
 
 // queryFn을 생략하고 바로 옵션으로 들어갈 수도 있습니다.
 function Post({ postId }) {
   const { status, data, error, isFetching } = useQuery(`/posts/${postId}`, {
     enabled: !!postId,
   })
 
   // ...
 }

기본 queryFn을 재정의하려는 경우 평소처럼 직접 제공하면 됩니다.

profile
풀스택 개발 중...

3개의 댓글

comment-user-thumbnail
2021년 11월 3일

한국어로 된 페이지가 없어서 고생했는데, 보기쉽게 잘 정리해주셨네요 감사합니다 :)

답글 달기
comment-user-thumbnail
2021년 12월 21일

감사합니다

답글 달기
comment-user-thumbnail
2022년 1월 23일

감사합니다

답글 달기