July 11, 2022
리액트로 개발을 하다보면 외부의 데이터를 가지고 있는 서버에게 비동기적으로 데이터를 요청하고, 그 데이터를 상태에 저장하는 일이 많이 생긴다.
보통 이런 데이터는 loading
, error
등 여러 플래그 변수도 같이 갖고 있어서 데이터를 요청하기 위해 작성해야 하는 공통적인 코드가 많아지는데,
이런 관리를 좀 편하게 해줄 방법은 없을지 알아보다가 Data Fetching에 관한 라이브러리로 swr
, react-query
, RTK-query
등이 있다는 걸 알게 되었다.
처음 봤을 때 이 중에서 react-query
가 괜찮아보여서 한번 학습해 봤고 공부한 내용을 나중에 찾아볼 수 있게 기록하고자 글을 작성했다.
공식 페이지에 따르면
TS/JS를 위한 강력한 비동기 상태 관리 도구라고 한다.
외부 서버로부터 가져오는 데이터는 클라이언트가 제어할 수 없기 때문에 특정 시점에 서버로부터 비동기적으로 가져오게 된다.
그렇기 때문에 시간이 지났을 때 실제로 서버가 가지고 있는 정보와 다를 가능성이 있어서 주기적으로 업데이트 해줘야 하고,
가져오는데 시간이 걸리거나 실패하는 등 조건에 따른 처리도 해줘야 하는데 이런 번거로운 작업을 손쉽게 하도록 해주는 라이브러리이다.
cacheTime
데이터가 inactive 상태일 때 얼마나 오랫동안 캐싱 데이터로 보관하고 있을지를 의미한다.
inactive 상태로 일정시간 지난다면 더 이상 사용하지 않는 데이터로 간주하고 가비지 컬렉터로 버려지는 것이다.
기본값은 5분이다.
staleTime
데이터를 fresh 상태에서 얼만큼의 시간이 지나야 stale 상태로 변화시킬지를 의미한다.
기본값은 0ms이다.
데이터를 조회하는 기능은 useQuery
를 사용한다.
useQuery(queryKey, queryFn, options)
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
예를 들어 위 키는 모두 다른 키이다.
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)
반면에 위처럼 배열에 객체 형태로 값이 들어있고, 그 객체가 단순히 내부 요소 순서만 다른 상황이라면 모두 같은 쿼리키로 인식한다.
staleTime: number
데이터를 fresh 상태에서 얼만큼의 시간이 지나야 stale 상태로 변화시킬지 ms 단위로 지정한다.
기본값 0
cacheTime: number
데이터가 inactive 상태일 때 얼마나 오랫동안 캐싱 데이터로 보관하고 있을지 ms 단위로 지정한다.
기본값 300000
refetchOnMount: boolean | 'always'
true: 데이터가 마운트 될 때 데이터가 stale 상태인 경우에만 새로 패칭함.
always: 데이터가 마운트 될 때 항상 새로 패칭함.
기본값 true
refetchOnWindowFocus: boolean | 'always'
true: 브라우저 창이 포커싱 될 때 데이터가 stale 상태인 경우에만 새로 패칭함.
always: 브라우저 창이 포커싱 될 때 항상 새로 패칭함.
기본값 true
refetchInterval: number
특정 시간마다 데이터를 새로 패칭함.
refetchIntervalInBackground: boolean
화면을 보고있지 않은 백그라운드 상황에서도 refetchInterval 기능을 사용함.
enabled: boolean
true: 데이터가 마운트되면 데이터를 패칭함.
false: 데이터가 마운트되어도 데이터를 패칭하지 않게 설정함.
기본값 true
onSuccess: (data) => {}
데이터 패칭에 성공했을 때 실행할 콜백 함수
onError: (error) => {}
데이터 패칭에 실패했을 때 실행할 콜백 함수
select: (data) => {}
패칭한 데이터를 useQuery hook의 data 객체에 넣기 전에 먼저 데이터를 가공할 수 있는 함수
데이터 생성, 수정, 삭제 등 변화를 주는 기능은 useMutation
을 사용한다.
useMutation(queryFn, option)
↓ TestForm.tsx
import React, { useState, useMemo, useCallback } from 'react';
import { useQueryClient, useMutation } from 'react-query';
import * as testApi from '@api/testApi';
import * as queryKey from '@util/queryKey';
function TestForm({}) {
const initialForm = useMemo(() => ({
author: '',
content: ''
}), []);
const [form, setForm] = useState(initialForm);
const queryClient = useQueryClient();
// 데이터 추가 요청
const postData = useMutation(testApi.postTestData, {
onSuccess: (result) => {
setForm(initialForm);
queryClient.invalidateQueries(queryKey.testListKeys.all);
},
onError: (err: Error) => {
console.error(err);
}
});
// input 상태 값 변경
const changeInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setForm({
...form,
[e.target.name]: e.target.value
});
}, [form]);
// 폼 전송 이벤트
const onSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault();
postData.mutate(form);
}, [form, postData]);
return (
<form onSubmit={onSubmit}>
<h1>작성 폼</h1>
<span>작성자 </span>
<input type="text" name="author" value={form.author} onChange={changeInput} />
<br/>
<span>내용 </span>
<input type="text" name="content" value={form.content} onChange={changeInput} />
<br/>
<button type="submit">전송</button>
{postData.isError && <div>{postData.error.message}</div>}
</form>
);
}
↓ @api/testApi.tsx
import axios from "axios";
export const postTestData = (data: object) => axios({
method: 'post',
url: `http://localhost:5000/api/test/data`,
data
});
↓ @util/queryKey.tsx
export const testListKeys = {
all: ['todos'] as const,
detail: (id: number) => [...testListKeys.all, id]
}