React Query

Yooncastle·2022년 11월 16일
0

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

사용하는 이유

클라이언트 데이터와 서버 데이터가 공존하는 문제
서버에는 이미 패치된 데이터가 클라이언트에서는 패치되기 전 데이터로 남아있을 수 있음

react-query는 서버와 클라이언트 데이터를 분리함

장점

  • Caching
  • update하면 get이 자동 실행
  • 데이터가 오래되었다고 판단되면 다시 get (invalidateQueries)
  • 중복호출 시 한번만 요청 ( 중복 호출 허용 시간 조절 가능 )
  • 무한 스크롤 ( Infinite Queries )
    react query useInfiniteQuery + react-intersection-observer
    Intersection Observer

    getNextPageParam
    : lastPage를 인자로 전달 받아서 다음 페이지가 존재할 경우 다음페이지 값을 fetchNextPage함수의 인자로 전달
    fetchNextPage = useInfiniteQuery의 두번째 콜백 함수 + getNextPageParam

  • 비동기 과정을 선언적으로 관리? ( 직관적인 API 호출 코드 )
  • API 처리에 관한 각종 인터페이스 및 옵션 제공
  • Devtool 제공
  • react hook과 사용하는 구조 유사

단점

  • Component가 상대적으로 비대해짐 ( Component 설계/분리에 대한 고민 필요 )
  • 프로젝트 설계가 어려워짐 ( Component 유착 최소화? 및 사용처 파악 필요 )

Query 상태흐름

default config

  • staleTime : 0
    cached data는 바로 stale 상태로

  • refetchOnMount refetchOnWindowFocus refetchOnReconnect : true
    각 시점에서 data가 stale이라면 항상 refetch 발생

  • cacheTime : 60 * 5 * 1000
    5분동안은 메모리에 존재

  • retry : 3
    서버 요청 실패 시 재요청 횟수

Server State를 전역상태처럼 관리

QueryClient가 내부적으로 Context를 사용하기 때문

useQuery

GET

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>
  );
}
 

첫번 째 파라미터는 unique key

  • https://www.zigae.com/react-query-key/
  • 다른 컴포넌트에서도 사용 가능
  • string 또는 배열
  • 배열일 경우 index 0번 값은 string, 두번째는 query함수 내부의 파라미터

두번 째 파라미터는 api 호출 함수 (비동기)

return 값

  • api 호출 함수 return 값
  • api 호출 성공 실패 여부

비동기로 동작

  • 여러개의 비동기 query가 있다면 useQueries 사용

enabled를 사용하면 동기적으로 사용가능

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

useQueries

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

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

promise.all 처럼 useQuery를 실행
하나의 배열에 각 쿼리에 대한 상태 값이 객체로 반환됨

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함수 내부에서 변수로 사용 가능

 const { data: notice } = useQuery(
    ['fetchNotice', id],
    () => fetchNotice(id),
    { enabled: !!token, staleTime: 60 * 60 * 1000 },
  )

 export const fetchNotice = async (id): Promise<string> => {
  const {
    data: { notice },
  } = await api.get(`/notice/${id}`)

  return notice
}

useMutation

POST

const { mutate: postNoticeMutate } = useMutation(postNotice, {
    mutationKey: 'postNotice',
    onSuccess: () => {
      // success!!
    },
    onError: (error: ErrorType) => {
	  // error!!     
    },
  })

const onClickSubmit = async (notice) => {
    await postNoticeMutate(notice)
  }

export const postNotice = async (notice) => {
	await api.post('/notice', { notice })
}

invalidateQueries

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

react query는 비동기를 조금 더? 선언적(선언형 프로그래밍 vs 명령형 프로그래밍, https://boxfoxs.tistory.com/430)으로 사용할 수 있음

을 더욱 직관적으로 할 수 있음

// 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>
);

Reference

https://kyounghwan01.github.io/blog/React/react-query/basic/

https://www.youtube.com/watch?v=MArE6Hy371c&t=6921s&ab_channel=%EC%9A%B0%EC%95%84%ED%95%9CTech

profile
기억보단 기록을

0개의 댓글