본문 바로가기
React

[React Query] React query 뿌수기

by dev수니 2024. 4. 9.
반응형

 React Query서버 상태 관리를 위한 라이브러리이다. 데이터 Fetching, Caching, 동기화, 서버 데이터 업데이트 등을 쉽게 만들어 준다.

리액트 쿼리를 사용하는 이유

  1. 간편한 데이터 관리: 데이터 가져오기, 캐싱, 동기화 및 업데이트 처리를 간편하게 할 수 있다.
  2. 실시간 업데이트 및 동기화
  3. 데이터 캐싱: 불필요한 api 요청을 줄인다.
  4. 서버 상태 관리: 로딩중, 에러, 성공 등의 상태를 간편하게 처리 할 수있다.
  5. 간편한 설정: @tanstack/react-query @tanstack/react-query-devtools 를 설치 한 후 최상위 컴포넌트에 QueryClientProvider 로 감싸주면 끝난다.

React Query 의 라이프 사이클

  • 쿼리 인스턴스가 mount됨
  • 네트워크에서 데이터 fetch 하고 query key 로 캐싱함
  • 이 데이터는 fresh 상테에서 staleTime(기본값0) 이후 stale 상태로 변경됨
  • A 쿼리 인스턴스가 unmount 됨
  • 캐시는 chacheTime(기본값 5min) 만큼 유지되다가 가비지 콜렉터로 수집됨.
  • 만약, chacheTime 이 지나기 전에 A 쿼리 인스턴드가 새롭게 mount 되면, fetch 가 실행되고 fresh 한 값을 가져오는 동안 캐시 데이터를 보여줌.

Query

function Todos() {
  const { isPending, isError, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
    staleTime: 5000,
    retry: 10,
    initialData: initialTodos,
  })

  if (isPending) {
    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>
  )
}

useQuery({ queryKey, queryFn, [...options] })

  1. queryKey : 쿼리의 고유 키로, 어플리케이션 전체에서 쿼리를 다시 가져오고, 캐싱하고, 공유하는 데 내부적으로 사용된다.
    1. 쿼리 키는 가져오는 데이터를 고유하게 설명하므로 쿼리 함수에서 사용하는 모든 변수를 포함해야 한다 .
    2. 쿼리 키는 객체의 키 순서 관계없이 해시된다.
  2. queryFn: Promise를 반환하는 모든 함수
  3. staleTime : 데이터가 한번 fetch 되고 나서 staleTime이 지나지 않았다면 unmount 후 mount 되어도 fetch가 일어나지 않는다.
  4. cacheTime : cacheTime이 지나기 전에 쿼리 인스턴스가 다시 마운트 되면, 데이터를 fetch하는 동안 캐시 데이터를 보여준다.
  5. refetchOnWindowFocus : 사용자가 애플리케이션 창을 떠났다가 돌아왔을 경우 자동으로 백그라운드에서 새로운 데이터를 요청한다. 이는 QueryClient 에서도 전역적으로도 사용할 수 있다. 
  6. retry : [ boolean, number, () => boolean | number ] 쿼리가 실해할 경우 재시도 횟수를 설정한다. default = 3
  7. initialData : 쿼리에 대한 초기 데이터가 이미 있고, 이를 퉈리에 직접 제공하는 경우 사용한다.
  8. return : 쿼리에 대한 모든 정보가 포함되어있다.
    1. 오류 발생 및 처리
      1. 쿼리에 오류가 있는지 확인하려면 거부된 Promise 를 던지거나 반환해야 한다.
      2. throw되지 않는 클라이언트와 함께 사용하는 경우 스스로 던져야한다. ex) axios, graphql
useQuery({
  queryKey: ['todos', todoId],
  queryFn: async () => {
    const response = await fetch('/todos/' + todoId)
    if (!response.ok) {
      throw new Error('Network response was not ok')
    }
    return response.json()
  },
})

Parallel Queries

수동 : 병렬 쿼리 수가 변경되지 않으면 아래와 같이 나란히 나열하여 사용한다.

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

 

동적 : 실행해야 하는 쿼리 수가 렌더링마다 변경되는 경우 hook 규칙을 위반한다.

원하는 만큼 많은 쿼리를 동적으로, 병렬로 실행하는 경우 useQueries hook 을 사용한다.

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

 

Dependent Queries

종속 쿼리는 실행되기 전에 완료하기 위해 이전 쿼리에 의존한다.

유의점으로 종속쿼리 요청은 waterfall 형식으로 성능을 저하시킨다. 대기 시간이 긴 클라이언트에서 발생할 때 특히 해롭나. 가능하면 두 쿼리를 병렬로 가져올 수 있도록 백엔드 API 를 재구성 하는 것이 좋다.

1. useQuery 종속 쿼리

enabled option을 사용하여 이전 쿼리에 의존한다.

// Get the user
const { data: user } = useQuery({
  queryKey: ['user', email],
  queryFn: getUserByEmail,
})

const userId = user?.id

// Then get the user's projects
const {
  status,
  fetchStatus,
  data: projects,
} = useQuery({
  queryKey: ['projects', userId],
  queryFn: getProjectsByUser,
  // The query will not execute until the userId exists
  enabled: !!userId,
})

 

2. useQueries 종속 쿼리

// Get the users ids
const { data: userIds } = useQuery({
  queryKey: ['users'],
  queryFn: getUsersData,
  select: (users) => users.map((user) => user.id),
})

// Then get the users messages
const usersMessages = useQueries({
  queries: userIds
    ? userIds.map((id) => {
        return {
          queryKey: ['messages', id],
          queryFn: () => getMessagesByUsers(id),
        }
      })
    : [], // if users is undefined, an empty array will be returned
})

Mutation

// This will not work in React 16 and earlier
const CreateTodo = () => {
  const mutation = useMutation({
    mutationFn: (event) => {
      event.preventDefault()
      return fetch('/api', new FormData(event.target))
    },
  })

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

// This will work
const CreateTodo = () => {
  const mutation = useMutation({
    mutationFn: (formData) => {
      return fetch('/api', formData)
    },
  })
  const onSubmit = (event) => {
    event.preventDefault()
    mutation.mutate(new FormData(event.target))
  }

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

useMutation({ mutationFn })

const CreateTodo = () => {
  const mutation = useMutation({
    mutationFn: (formData) => {
      return fetch('/api', formData)
    },
  })
  const onSubmit = (event) => {
    event.preventDefault()
    mutation.mutate(new FormData(event.target))
  }

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

 

'useMutation'은 mutation life cycle 중 쉽게 side effect 를 처리할 수 있는 헬퍼들을 제공한다.

useMutation({
  mutationFn: 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!
  },
})
반응형

댓글