본문 바로가기

FrontEnd

리액트 쿼리와 비교한 RTK Query와 Mutation(뮤테이션), 캐싱 동작

반응형

사내 프로젝트에서 Redux Toolkit을 도입하였기에, 주로 사용하던 Redux Query 대신에 RTK Query를 사용해 보았다.

RTK Query와 React Query의 차이점 : 큰 그림

RTK Query는 프레임워크와 무관한 기능들만 포함한다.

RTK Query는 React Query의 대부분의 페칭 기능들을 포함하고 있다.

하지만 React Query의 명칭이 의미하는 것처럼 React Query는 Hook과 React에 최적화된 몇몇 기능들을 포함하고 있다.

  • useInfiniteQuery
  • Suspense및 ErrorBoundary

따라서 무한 스크롤이나 서스펜스가 필요하다면 Wrapper 코드를 작성해야 한다.

(보니까 Apollo-Client도 유사하다.)

기능 비교표는 아래 링크를 참조한다.

https://redux-toolkit.js.org/rtk-query/comparison

 

Comparison with Other Tools | Redux Toolkit

RTK Query > Comparison: Compares features and tradeoffs vs other similar tools

redux-toolkit.js.org

기본 캐시 전략이 다르다.

한 예를 들면 리액트 쿼리는 디폴트 staletime이 0초다.

같은 쿼리를 구독하는 컴포넌트가 마운트 될 때마다 데이터를 다시 가져온다.

 

RTK Query는 모든 구독 컴포넌트가 언마운트 된지 60초가 지나야 해당 쿼리를 stale한 것처럼 처리한다.

관련 내용은 아래 링크를 참조한다.

https://redux-toolkit.js.org/rtk-query/usage/cache-behavior

 

Cache Behavior | Redux Toolkit

RTK Query > Usage > Cache Behavior: defaults, cache lifetimes, and tradeoffs

redux-toolkit.js.org


React Query와 차이점 : 디테일

React Query의 특정 엔드포인트에 대한 useQuery및 useMutation 함수, fetcher는 독립적이다.

따라서 특정 엔드포인트에 대한 쿼리, 뮤테이션은 별도로 존재한다.

또한 각 쿼리, 뮤테이션을 위한 fetcher는 독립적인다.

RTK Query는하나의 Slice에  공통 fetcher 하나가 존재한다.

이는 기본적으로 제공하는 fetchBaseQuery(내부적으로 fetch 혹은 XMLHttpRequest 알아서 사용해줌)가 될 수도 있지만,

Axios를 사용하고 싶으면 사용자 정의하여 사용할 수 있다.

참고로 커스터마이징이 괴랄하다. 방법은 아래 링크를 참조한다.

https://redux-toolkit.js.org/rtk-query/usage/customizing-queries

 

Customizing Queries | Redux Toolkit

RTK Query > Usage > Customizing Queries: overriding default query behavior

redux-toolkit.js.org

인증/인가, 공통 에러 처리가 필요한 경우 울며 겨자먹기로 커스터마이징 해서 사용할 수밖에 없다.

사내 프로젝트에서는 Service 레이어를 프론트엔드에서 한 번 wrapping해서 사용하므로,

fetchApiBaseQuery라는 함수를 정의해서 사용하였다.

React Query는 useQuery및 useMutation을 별도의 queryKey로 연결한다.

아래 게시물 참조

https://itchallenger.tistory.com/582

 

리액트 쿼리 : 쿼리키

원문 : https://tkdodo.eu/blog/effective-react-query-keys 쿼리 키 라이브러리가 내부적으로 데이터를 올바르게 캐시하기 위해 필요합니다. 쿼리 종속성이 변경될 때 자동으로 다시 가져올 수 있도록 하기 위

itchallenger.tistory.com

RTK Query는 queryKey 역할을 하는 tag를 slice 내에 포함한다.

아래 코드도 괴랄해 보이지만 별거 없다.

 

1. 일단 각 쿼리, 뮤테이션에서 invalidate할 tagTypes를 선언한다.

2. 각 쿼리, 뮤테이션에서 tagTypes내에 있는 것 중 제공 태그, 무효화 대상 태그를 각각 선언한다.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'

const api = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: '/',
  }),
  tagTypes: ['Post', 'User'],
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({
      query: () => '/posts',
      providesTags: (result, error, arg) =>
        result
          ? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
          : ['Post'],
    }),
    getUsers: build.query<User[], void>({
      query: () => '/users',
      providesTags: ['User'],
    }),
    addPost: build.mutation<Post, Omit<Post, 'id'>>({
      query: (body) => ({
        url: 'post',
        method: 'POST',
        body,
      }),
      invalidatesTags: ['Post'],
    }),
    editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
      query: (body) => ({
        url: `post/${body.id}`,
        method: 'POST',
        body,
      }),
      invalidatesTags: (result, error, arg) => [{ type: 'Post', id: arg.id }],
    }),
  }),
})

뭔가 복잡해보이지만 아래 표를 보면 간단하다.

  • React Query는 일반적인 키를 이용한 무효화가 구체적인 키의 무효화를 유발하는 퍼지 매칭을 사용한다.
    • 쿼리키를 사용
    • 예를 들어 [{type:"person"}]는  [{type:"person",id:1}]을 같이 무효화한다.
      • [{type:"person",id:1}]은 해당 키를 가진 쿼리만 무효화한다.
  • RTK Query는 구체적인 태그를 제공하면, 구체적인 쿼리의 데이터를 포함한 일반적인 태그를 가진 쿼리를 무효화한다.
    • 태그를 사용한다
      • [{type:"person",id:1}]은 ["person"] 태그를 제공하는 쿼리를 무효화한다.
        • [{type:"person",id:2}]와 같이 일반화 할 수 없는 경우는 무효화하지 않는다.
      • ["person"]은 ["person"]만 무효화한다.

보통 개별 요소를 업데이트 하는 경우, 전체 조회를 업데이트 해야 하는 경우가 많기 때문에,

RTK 쿼리의 방식이 좀 더 자연스럽고 편리한 듯 하다.

쿼리 무효화 테이블

RTK Query는 쿼리와 뮤테이션의 colocation을 강요하는데, 처음 보기엔 불편해 보이지만,

각 엔드포인트를 store로 생각하면, pub,sub구조에 업데이트 책임을 스토어에 부여한 well-architected 구조인 듯 하다.


RTK Query를 사용하면서 느낀 점 : 개발자 경험(DX)

RTK Query는 라이브러리 개발자들이 처음부터 생각하고 있던 best practice와 clean architecture를 강요하는 구조로 되어 있다.

즉, immer를 기본으로 지원하는 등, 라이브러리 개발자들이 생각하는 좋은 라이브러리들을 내장하고 있다.

이는 초기 세팅의 자유도를 낮추는 대신, 나중에 기능을 확장할 때 확실히 이점이 있다.

 

또한 react-query를 사용해 mutation, query를 개발하는 것보다,

특정 endpoint에 대한 query, mutation을 개발하는 속도 및 편의성이 나았던 것 같다.

같은 관심사를 다루는 코드들이 같은 모듈에 존재하며, 중복 작업이 최소화되기 때문인 듯 하다.


추가로 볼만한 게시물

https://gist.github.com/Shrugsy/6b6af02aef1f783df9d636526c1e05fa

 

RTK Query cache utils. Useful abstractions for creating `provides`/`invalidates` cache data tags against endpoints.

RTK Query cache utils. Useful abstractions for creating `provides`/`invalidates` cache data tags against endpoints. - rtkQueryCacheUtils.ts

gist.github.com

 

반응형