[React] reactQuery

Jnary·2024년 4월 11일
0

React

목록 보기
9/10
post-thumbnail

React Query란?

  • fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리
  • react query를 사용하는 이유
    1. React Application에서 서버 상태를 불러오고, 캐싱하며, 지속적으로 업데이트하는 작업을 도와주는 라이브러리
    2. 복잡하고 장황한 코드가 필요한 다른 데이터 불러오기 방식과 달리, React Component 내부에서 간단하고 직관적으로 API 사용 가능
    3. React Query에서 제공하는 캐싱, Window Focus Refetching 등 다양한 기능을 활용하여 API 요청과 관련된 번잡한 작업 없이 “핵심 로직”에 집중 가능
  • Client Data와 Server Data 분리
    • Client Data : 모달 관련 데이터, 페이지 관련 데이터 등등
    • Server Data : 비동기 API 호출을 통해 불러오는 데이터
    • 리액트 상태 관리 라이브러리 : redux, recoil 등
      • 대부분 클라이언트 상태 + 서버상태 함께 담아두고 있음
      • Client Data를 관리하는 로직이 집중되어있음
      • Server Data까지 효율적으로 관리하는 데에 한계 존재
    • react-query를 사용해 서버 상태를 관리
      • 클라이언트 상태와 분리 가능
      • 직관적, 효율적인 관리 가능

React Query 장점

  1. Data Fetching 로직 단순화
    • 기존
      • Fetching 코드 작성

      • 데이터를 담아 둘 상태 생성

      • useEffect 이용해 컴포넌트 Mount 시 데이터를 Fetching 한 뒤 상태 저장

        const getServerData = async() => {
        	const data = await fetch ( "..." )
        	                     .then((res) => res.json());
        	return data;
        };
        
        export default function App() {
        	const [state, setState] = useState([]);
        	useEffect(() => {
        		getServerData()
        			.then((dataList) => setState(dataList))
        			.catch((e) => setState([]));
        	}, []);
        	...
        }
      • useEffect : Side Effect 발생 → 흐름 파악 어렵게

    • react-query 사용
      export default function App() {
      	const { data } = useQuery(["data"], getServerData);
      • Data Fetching 방식 규격화
  2. 캐싱 Caching
    • 캐싱 : 특정 데이터의 복사본을 저장하여 이후 동일한 데이터에 다시 접근할 때, 속도를 높이는 것
    • 반복적인 비동기 데이터 호출 방지
    • 불필요한 API 호출 방지 → 서버에 대한 부하 감소
    • 데이터 변경 감지? 언제 데이터 갱신?
      • 사용자가 화면 보고 있을 때
      • 페이지 전환이 일어났을 때
      • 페이지 전환 없이 이벤트가 발생해 데이터를 요청할 때
    • 옵션
      • 브라우저에 포커스가 들어온 경우

        refetchOnWindowFocus default: true

      • 새로운 컴포넌트 마운트가 발생한 경우

        refetchOnMount default: true

      • 네트워크 재연결이 발생한 경우

        refetchOnReconnect default: true

      • 캐시 데이터의 유통기한 지정

        staleTime default값 : 0

        → 주로 애플리케이션의 데이터 새로고침 빈도 조절에 사용

      • 저장한 데이터를 얼마나 유지할 지 지정

        `gcTime` default값 : 5분
        
        → garbage collection time
        
        → 캐시된 데이터가 메모리에서 제거되기 전에 비활성 상태로 유지되는 시간
        
        → 메모리 사용량을 최적화하고 캐시에서 데이터를 언제 제거할 지 결정하는 데 사용
        const { data } = useQuery(['data', getServerData, {
        	staletime: 10 * 60 * 1000;
        	cachetime: 10 * 60 * 1000;
        })

        → 별다른 refresh가 없을 때, 10분 내 재호출시 API 호출X, 캐싱된 데이터 제공

  3. 동기적 실행
    • 기존 : useEffect 로 동기적으로 관리하기 어려움
    • react-query enabled 옵션
      • 조건 충족될 때만 API 호출

        const {data: data1} = useQuery(["data1"], getServerData);
        const {data: data2} = useQuery(["data2", data1], getServerData, {
        	enabled: !!data1
        });

React Query 단점

  • 소규모 프로젝트에서 사용 시 프로젝트 규모에 비해 복잡성이 추가될 수 있음
  • 모든 케이스에서 적합한 것은 아니기 때문에 React Query의 도입 여부 따져보고 가치가 있을 때 도입해야 함

사용법

  1. React Query 설치

    npm install @tanstack/react-query

  2. React Query Client 설정

    • QueryClient 인스턴스를 생성
    • 이를 애플리케이션의 최상위에서 제공
    • 이를 통해 React Query의 상태와 캐시를 관리 가능
  3. QueryClientProvider를 사용하여 애플리케이션에 React Query 제공

    • 생성한 QueryClient 인스턴스를 QueryClientProvider를 사용하여 애플리케이션에 연결
    • 이는 일반적으로 애플리케이션의 최상위 컴포넌트에서 수행됩니다.
  4. React Query의 Hooks 사용

    • useQuery, useMutation 등의 React Query 훅 사용
import {QueryClient, QuertClientProvider} from 'react-query';
const queryClient = new QueryClient();
function App() {
	return {
		<QueryClientProvider client={queryClient}>
			...
		</QueryClientProvider>
	};
}

여러 가지 기능들

  • GET : useQuery
  • PUT, UPDATE, DELETE : useMutation
  • useQuery
    • 첫 번째 파라미터
      • 첫 요소 : unique key
      • 두 번째 요소 : query 함수 내부의 파라미터로 값 전달
    • 두 번째 파라미터
      • 실제 호출하는 비동기 함수
      • Promise를 반환하는 형태
    1. queryKey (queryKey)
      • queryKey는 쿼리의 고유 식별자
      • react Query가 내부적으로 캐싱과 데이터 갱신을 관리하는 데 사용
      • queryKey는 문자열 또는 객체, 배열 등이 될 수 있음
      • 주로 요청하는 데이터를 유일하게 식별할 수 있는 값을 사용
      • 예를 들어, 특정 ID로 포스트를 가져오는 쿼리의 경우 ['post', postId]와 같이 배열을 사용하여 queryKey를 구성할 수 있습니다. 이렇게 배열을 사용하는 경우, 배열의 각 요소는 쿼리 키의 일부로 사용되어 쿼리의 고유성을 보장합니다.
    2. query function (queryFn)
      • queryFn은 비동기 데이터를 가져오는 함수
      • 실제 데이터를 요청하는 로직을 포함
      • 이 함수는 비동기로 작동, 보통 API 호출을 수행해 결과를 반환
      • queryFnqueryKey를 매개변수로 받을 수 있으며, 이를 통해 데이터 요청 시 필요한 매개변수를 전달받을 수 있습니다. 예를 들어, queryKey['post', postId]인 경우, queryFn은 이 postId를 사용하여 특정 포스트의 데이터를 요청할 수 있습니다.
    • 최종반환값 : API 성공실패 여부, 반환 값을 포함한 객체
      • 자주 사용되는 반환값
        • data
          • API요청 함수를 통해 응답 받은 데이터
          • status = ‘pending’이면 data는 무조건 undefined
        • error
          • API 요청 함수에서 error 발생 시 생성되는 Error객체
        • isPending: boolean, status에서 파생된 값
          (
          status == ‘pending’)
          - 쿼리에 캐싱해놓은 데이터가 없음을 의미함
          - 데이터 가져오기 전에는 true
          - 데이터를 가져온 후로는 계속 false (refetch를 하는 경우에도)
          - 만약 직접 캐싱된 데이터를 제거했다면 true
          - 데이터를 갖고 있는지 여부만 확인하기 때문에
          fetchStatus == 'paused'일 경우에도 true라서
          로딩 spinner를 표시할 조건 값으로 사용하기엔 부적합함
        • isFetching: boolean, fetchStatus에서 파생된 값
          (
          fetchStatus **==** ‘fetching’)
          - 데이터 유무에 관계없이, 로딩 중임을 의미함
          - 어떤 상황이든 데이터 로딩 중이면 true
          - 데이터가 업데이트 중인지 표시하려면 이 값을 사용
        • isLoading: boolean, status & fetchStatus에서 파생된 값
          (
          isFetching && isPending)
          - 쿼리에 캐싱해놓은 데이터가 없고, 로딩 중임을 의미함
          - 페이지 로딩 초기에 로딩 spinner를 표시하려면 이 값을 사용
          - isPending과 달리 인터넷 연결까지 확인하기 때문
        • isRefetching: boolean, status & fetchStatus에서 파생된 값
          (
          isFetching && **!**isPending)
          - 쿼리에 캐싱해놓은 데이터가 있고, 로딩 중임을 의미함
          (업데이트)

          - 댓글 갱신 같은 데이터 새로 고침에 로딩 spinner를 표시하려면 이 값을 사용
    • status가 변할 때 마다 useQuery를 사용 중인 컴포넌트를 리렌더링 함
      • 처음 렌더링때 status = “pending” (초기데이터 설정시 success)

      • 도중에 에러 발생 시 리렌더링 및 status = “error”

      • 데이터를 잘 받아왔으면 리렌더링 및 status = “success”

        → 추가적인 업데이트가 없다면 총 2번 리렌더링 됨

        → 추가적인 업데이트가 있을 때 마다 리렌더링

        console.log로 출력한 useQueryResult객체의 몇 가지 속성들

  • useMutation
    • 값을 변경할 때 사용할 수 있는 API

      const mutation = useMutation({
      	mutationFn: (newTodo) => {
      		return axios.post('/todos', newTodo)
      	},
      })
      
      return (
      	<div>
      		{mutation.isLoading ? (
      			'Adding todo ...'
      		) : (
      			<>
      				{mutation.isSuccess ? <div> Todo added! </div> : null}
      				<button onClick={() => {
      					mutation.mutate({id: new Date(), title: 'Write Diary' })
      					}}
      				>
      					Create Todo
      				</button>
      			</>
      		)}
      	</div>
      )
    • optimistic update ✅

      • 클라이언트 상태 관리

      • 서버 요청이 완료되기 전에 클라이언트 측에서 데이터를 미리 업데이트하여 사용자에게 즉각적인 피드백을 제공하는 방법

      • 데이터 수정 액션 → 서버에 요청 → 응답 대기 :(

      • 서버 응답 오기 전에 클라이언트 측에서 데이터를 미리 업데이트

      • 서버 요청 실패, 예상치 못한 응답 → 미리 업데이트한 상태를 롤백 필요 (추가적인 로직 필요)

      • 이를 쉽게 구현할 수 있는 도구 제공

        import { useMutation, useQueryClient } from 'react-query';
        
        // 가정: 서버에 데이터를 업데이트하는 비동기 함수
        const updateData = async (newData) => {
          // API 요청을 통해 서버에 데이터 업데이트
          // 여기서는 예시이므로 실제 API 호출 코드는 생략합니다.
        };
        
        const MyComponent = () => {
          const queryClient = useQueryClient();
        
          const { mutate } = useMutation(updateData, {
            // Optimistic Update를 실행할 함수
            onMutate: async (newData) => {
              // 이전 상태를 캐싱해둡니다.
              const previousData = queryClient.getQueryData('myData');
        
              // 상태를 즉각 업데이트합니다.
              queryClient.setQueryData('myData', old => ({ ...old, ...newData }));
        
              // 롤백을 위해 이전 상태를 반환합니다.
              return { previousData };
            },
            // Mutation이 성공했을 때
            onSuccess: () => {
              // 데이터가 성공적으로 업데이트되었으므로, 별도의 액션이 필요 없습니다.
            },
            // 에러 발생 시
            onError: (err, newData, context) => {
              // 에러가 발생하면 이전 상태로 롤백합니다.
              queryClient.setQueryData('myData', context.previousData);
            },
            // Mutation이 완료(성공 또는 실패)된 후
            onSettled: () => {
              // 모든 상황에 대해 refetch를 실행하여 최신 상태를 유지합니다.
              queryClient.invalidateQueries('myData');
            },
          });
        
          const updateMyData = (data) => {
            mutate(data);
          };
        
          return (
            // UI 컴포넌트에서 updateMyData 함수를 사용하여 데이터 업데이트를 실행할 수 있습니다.
            <button onClick={() => updateMyData({ key: 'value' })}>Update Data</button>
          );
        };
        
        export default MyComponent;
        
profile
숭실대학교 컴퓨터학부 21

0개의 댓글