사내 프로젝트에서 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
기본 캐시 전략이 다르다.
한 예를 들면 리액트 쿼리는 디폴트 staletime이 0초다.
같은 쿼리를 구독하는 컴포넌트가 마운트 될 때마다 데이터를 다시 가져온다.
RTK Query는 모든 구독 컴포넌트가 언마운트 된지 60초가 지나야 해당 쿼리를 stale한 것처럼 처리한다.
관련 내용은 아래 링크를 참조한다.
https://redux-toolkit.js.org/rtk-query/usage/cache-behavior
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
인증/인가, 공통 에러 처리가 필요한 경우 울며 겨자먹기로 커스터마이징 해서 사용할 수밖에 없다.
사내 프로젝트에서는 Service 레이어를 프론트엔드에서 한 번 wrapping해서 사용하므로,
fetchApiBaseQuery라는 함수를 정의해서 사용하였다.
React Query는 useQuery및 useMutation을 별도의 queryKey로 연결한다.
아래 게시물 참조
https://itchallenger.tistory.com/582
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"]만 무효화한다.
- [{type:"person",id:1}]은 ["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
'FrontEnd' 카테고리의 다른 글
Leetcode 241: Different Ways to Add Parentheses(수식 모든 경우의 수 계산하기)[BFS,DFS] (0) | 2023.04.03 |
---|---|
프론트엔드 클린 아키텍처 with React (0) | 2023.04.02 |
[번역] Mobile First Design vs Desktop First Design (0) | 2023.03.29 |
[번역] min(), max(), clamp() CSS 함수 (0) | 2023.03.28 |
[JS] 한글을 2byte로 한 줄 길이 제한 및 줄바꿈 코드 (0) | 2023.03.24 |