230104 항해99 59일차 react-query

요니링 컴터 공부즁·2023년 1월 11일
0
post-custom-banner
  • react-query는 서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러 핸들링 등 비동기 과정을 더욱 편하게 하는데 사용된다.
  • react-query를 사용함으로 서버, 클라이언트 데이터를 분리한다.

react-query 장점

  • 캐싱
  • get을 한 데이터에 대해 update를 하면 자동으로 get을 다시 수행(ex. 게시판의 글을 가져왔을 때 게시판의 글을 생성하면 게시판 글을 get하는 api를 자동으로 실행)
  • 데이터가 오래 되었다고 판단되면 다시 get(invalidateQueries)
  • 동일 데이터 여러번 요청하면 한번만 요청(옵션에 따라 중복 호출 허용 시간 조절 가능)
  • 무한 스크롤 (Infinite Queries)
  • 비동기 과정을 선언적으로 관리
  • react hook과 비슷한 사용 구조

세팅하기

  1. react-query 설치
yarn install react-query
  1. root 파일에 react-query 세팅
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      {/* devtools */}
      <ReactQueryDevtools initialIsOpen={true} />
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);
 

api

useQuery

  • 데이터를 get 하기 위한 api
  • 첫번째 파라미터로 unique Key가 들어가고, 두번째 파라미터로 비동기 함수(api호출 함수)가 들어간다. (두번째 파라미터는 promise가 들어가야한다.)
  • 첫번째 파라미터로 설정한 unique Key는 다른 컴포넌트에서도 해당 키를 사용하면 호출 가능하다. unique Key는 string과 배열을 받는다. 배열로 넘기면 0번 값은 string값으로 다른 컴포넌트에서 부를 값이 들어가고, 두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달된다.
  • return 값은 api의 성공, 실패여부, api return 값을 포함한 객체이다.
  • useQuery는 비동기로 작동한다. 즉, 한 컴포넌트에 여러개의 useQuery가 있다면 하나가 끝나고 다음 useQuery가 실행되는 것이 아닌 두개의 useQuery가 동시에 실행된다. 여러개의 비동기 query가 있다면 useQuery보다는 useQueries를 사용한다.
  • enabled를 사용하면 useQuery를 동기적으로 사용 가능하다.

ex)

const Todos = () => {
  const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
    refetchOnWindowFocus: false, // react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행한다. 그 재실행 여부 옵션이다.
    retry: 0, // 실패시 재호출 몇번 할지
    onSuccess: data => {
      // 성공시 호출
      console.log(data);
    },
    onError: e => {
      // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출된다.)
      // 강제로 에러를 발생시키려면 api단에서 throw Error를 날린다. 
      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>
  );
};
  • isLoading, isSuccess 말고 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의 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를 비동기로 여러개 실행할 경우 모든 변수를 개발자는 다 기억해야하고 모든 변수에 대한 로딩, 성공, 실패처리를 해야한다.
  • 이때 promise.all처럼 useQuery를 하나로 묶을 수 있는데, 그것이 useQueries다. promise.all과 마찬가지로 하나의 배열에 각 쿼리에 대한 상태 값이 객체로 들어온다.
  • unique key를 배열로 넣으면 query함수 내부에서 변수로 사용 가능하다.
// 아래 예시는 롤 룬과, 스펠을 받아오는 예시다.
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]);

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

  • post, update 등 값을 바꿀때 사용하는 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 다시 실행

  • update후에 get 함수를 간단히 재실행 할 수 있다.
  • mutation 함수가 성공할 때, unique key로 맵핑된 get 함수를 invalidateQueries에 넣어주면 된다.
const mutation = useMutation(postTodo, {
  onSuccess: () => {
    // postTodo가 성공하면 todos로 맵핑된 useQuery api 함수를 실행한다.
    queryClient.invalidateQueries("todos");
  }
});
  • 만약 mutation에서 return된 값을 이용해서 get 함수의 파라미터를 변경해야할 경우 setQueryData를 사용한다.
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"
});

react Suspense와 react-query 사용하기

  • Suspense를 사용하며 loading을, Error boundary를 사용하여 에러 핸들링을 더욱 직관적으로 할 수 있습니다.
  • suspense를 사용하기 위해 QueryClient에 옵션을 하나 추가한다.(global하게 suspense를 사용한다고 가정할 때)
// 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")
);
  • 함수마다 suspense를 사용할때는 다음과 같이 사용한다.
const { data } = useQurey("test", testApi, { suspense: true });
  • Suspense는 다음과 같이 사용한다.
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>
);

참조:
react-query

post-custom-banner

0개의 댓글