React-Query | 개념 (concept)

Kate Jung·2022년 5월 17일
2

React

목록 보기
21/28
post-thumbnail
post-custom-banner

📌 개요

🔹 react-query 란?

서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러핸들링 등 비동기 과정을 더욱 편하게 하는데 사용

🔹 사용 이유

서버, 클라이언트 데이터를 분리

🔹 장점

주로 아래와 같이 프론트 개발자가 구현하기 귀찮은 일들을 수행

  • 캐싱

  • get을 한 데이터에 대해 update를 하면 자동으로 get을 다시 수행

  • 데이터가 오래 되었다고 판단되면 다시 get (invalidateQueries)

  • 동일 데이터 여러번 요청하면 한번만 요청 (옵션에 따라 중복 호출 허용 시간 조절 가능)

  • 무한 스크롤 (Infinite Queries (opens new window))

  • 비동기 과정을 선언적으로 관리 가능

  • react hook와 사용 구조 비슷

📌 설치

yarn install react-query

📌 세팅

src/index.js

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'

const queryClient = new QueryClient()

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools initialIsOpen={true} />
      <App />
    </QueryClientProvider>
  </React.StrictMode>
)

📌 useQuery

데이터를 get 하기 위한 api

  • post, update

    useMutation 사용

  • return 값

    객체 (api의 성공, 실패여부, api return 값을 포함)

🔹 파라미터

  • 첫번째: unique Key

    • unique Key

      • 다른 컴포넌트에서도 해당 키를 사용 시, 호출 가능.

      • string과 배열을 받음.

        배열로 넘기면 0번 값: string으로 다른 컴포넌트에서 부를 값이 들어감.

        두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달됨.

  • 두번째: 비동기 함수(api호출 함수. promise가 들어감.)

🔹 속성

  • refetchOnWindowFocus

    사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행.

    그 재실행 여부 옵션

  • retry

    실패 시, 재호출 몇 번 할지

  • onSuccess

    성공 시 호출

  • onError

    api 호출이 실패한 경우만 호출 (401, 404같은 error가 아님)

  • status로 한번에 처리 가능

    isLoading, isSuccess 말고

  • 예시

    • 기본

      import { useQuery } from 'react-query'
      import { fetchTodoList } from '../services/todo'
      
      const Todos = () => {
        const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
          refetchOnWindowFocus: false,
          retry: 0,
          onSuccess: data => {
            console.log(data);
          },
          onError: e => {
            console.log(e.message);
          }
        });
      
        if (isLoading) {
          return <span>Loading...</span>;
        }
      
        if (isError) {
          return <span>Error: {error.message}</span>;
        }
      
        return (
          <ul>
            {data.map(todo => (
              <li key={todo.id}>{todo.title}</li>
            ))}
          </ul>
        );
      };
    • 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>;
        }
      
        return (
          <ul>
            {data.map(todo => (
              <li key={todo.id}>{todo.title}</li>
            ))}
          </ul>
        );
      }
      

🔹 비동기로 작동

즉, 한 컴포넌트에 여러 개의 useQuery가 있다면 동시 실행함. 

단, 여러 개의 비동기 query가 있다면 useQueries 권유.

🔹 동기적으로 사용 가능

  • enabled 사용

  • useQuery의 옵션값(3번째 인자)에 enabled에 값을 넣으면 그 값이 true일때 useQuery 실행. 이것을 이용해서 동기적으로 함수 실행

  • 예시

    const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
    const { data: nextTodo, error, isFetching } = useQuery(
      "nextTodos",
      fetchNextTodoList,
      {
        enabled: !!todoList // true가 되면 fetchNextTodoList 실행
      }
    );

🔸 useQueries

useQuery를 하나로 묶기 가능.

  • 사용법

    하나의 배열에 각 쿼리에 대한 상태 값이 객체로 들어옴.

  • 예시

    • useQuery

      어짜피 세 함수 모두 비동기로 실행하는데, 세 변수를 개발자는 다 기억해야하고 세 변수에 대한 로딩, 성공, 실패처리를 모두 해야 함.

      const usersQuery = useQuery("users", fetchUsers);
      const teamsQuery = useQuery("teams", fetchTeams);
      const projectsQuery = useQuery("projects", fetchProjects);
    • useQueries

      // 롤 룬과, 스펠을 받아오는 예시
      const result = useQueries([
        {
          queryKey: ["getRune", riot.version],
          queryFn: () => api.getRunInfo(riot.version)
        },
        {
          queryKey: ["getSpell", riot.version],
          queryFn: () => api.getSpellInfo(riot.version)
        }
      ]);
      
      useEffect(() => {
        console.log(result); // [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}]
        const loadingFinishAll = result.some(result => result.isLoading);
        console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료
      }, [result]);

🔹 unique key 활용

unique key를 배열로 넣으면 query함수 내부에서 변수로 사용 가능. 그것을 활용.

// params를 주목

const riot = {
  version: "12.1.1"
};

const result = useQueries([
  {
    queryKey: ["getRune", riot.version],
    queryFn: params => {
      console.log(params); // 👉 {queryKey: ['getRune', '12.1.1'], pageParam: undefined, meta: undefined}

      return api.getRunInfo(riot.version);
    }
  },
  {
    queryKey: ["getSpell", riot.version],
    queryFn: () => api.getSpellInfo(riot.version)
  }
]);

📌 QueryCache

쿼리에 대해 성공, 실패 전처리 가능

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      console.log(error, query);
      if (query.state.data !== undefined) {
        toast.error(`에러가 났어요!!: ${error.message}`);
      },
    },
    onSuccess: data => {
      console.log(data)
    }
  })
});

📌 useMutation

값을 바꿀때 사용하는 api

  • return 값

    useQuery와 동일

🔹 예시

import { useState, useContext, useEffect } from "react";
import loginApi from "api";
import { useMutation } from "react-query";

const Index = () => {
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");

  const loginMutation = useMutation(loginApi, {
    onMutate: variable => {
      console.log("onMutate", variable);
      // variable : {loginId: 'xxx', password; 'xxx'}
    },
    onError: (error, variable, context) => {
      // error
    },
    onSuccess: (data, variables, context) => {
      console.log("success", data, variables, context);
    },
    onSettled: () => {
      console.log("end");
    }
  });

  const handleSubmit = () => {
    loginMutation.mutate({ loginId: id, password });
  };

  return (
    <div>
      {loginMutation.isSuccess ? "success" : "pending"}
      {loginMutation.isError ? "error" : "pending"}
      <input type="text" value={id} onChange={e => setId(e.target.value)} />
      <input
        type="password"
        value={password}
        onChange={e => setPassword(e.target.value)}
      />
      <button onClick={handleSubmit}>로그인</button>
    </div>
  );
};

export default Index;

🔹 update후에 get함수 재실행

▪︎ invalidateQueries

mutation 함수가 성공할 때, unique key로 맵핑된 get 함수를 invalidateQueries에 넣기

const mutation = useMutation(postTodo, {
  onSuccess: () => {
    // postTodo 성공 시, todos로 맵핑된 useQuery api 함수를 실행
    queryClient.invalidateQueries("todos");
  }
});

▪︎ setQueryData

만약 mutation에서 return된 값을 이용해서 get 함수의 파라미터를 변경해야할 경우 사용

const queryClient = useQueryClient();

const mutation = useMutation(editTodo, {
  onSuccess: data => {
    // data가 fetchTodoById로 들어간다
    queryClient.setQueryData(["todo", { id: 5 }], data);
  }
});

const { status, data, error } = useQuery(["todo", { id: 5 }], fetchTodoById);

mutation.mutate({
  id: 5,
  name: "nkh"
});

📌 Suspense

  • react-query 사용 이유 中 1

    비동기를 더 선언적으로 사용 가능

  • 더욱 직관적으로

  • 사용 방법

    • QueryClient에 옵션 하나 추가.

    • 예시

      • 글로벌하게 사용

        // src/index.js
        const queryClient = new QueryClient({
          defaultOptions: {
            queries: {
              retry: 0,
              suspense: true
            }
          }
        });
        
        ReactDOM.render(
          <React.StrictMode>
            <QueryClientProvider client={queryClient}>
              <App />
            </QueryClientProvider>
          </React.StrictMode>,
          document.getElementById("root")
        );
      • 함수마다 사용

        const { data } = useQurey("test", testApi, { suspense: true });
      • 세팅 완료 시, 사용

        return (
          // isLoading이 true이면 Suspense의 fallback 내부 컴포넌트가 보임
          // isError가 true이면 ErrorBoundary의 fallback 내부 컴포넌트가 보임
          <Suspense fallback={<div>loading</div>}>
            <ErrorBoundary fallback={<div>에러 발생</div>}>
              <div>{data}</div>
            </ErrorBoundary>
          </Supense>
        );

참고

profile
복습 목적 블로그 입니다.
post-custom-banner

0개의 댓글