akjfal

RTK 기본 개념 정리 본문

카테고리 없음

RTK 기본 개념 정리

akjfal 2023. 12. 3. 14:11

RTK Query를 왜 나왔냐?

💡 결론 : 데이터 fetching과 caching 로직을 줄이기 위해서!

데이터를 가져오는데는 해줘야 할 일이 많습니다.

  • 가져오는동안 로딩창
  • 중복데이터 호출도 고려
  • 최적화
  • 캐쉬도 관리

기존엔 상태 관리와 엮어서 해결하려던 부분을 별도의 작업이라고 생각하기 시작했습니다.

RTK Query는 createSlice랑 craeteAsyncThunk로 만들어진 리덕스 라이브러리입니다.. 그리고 Query를 통해서 데이터 로딩 등 컴포넌트를 만들고 화면을 구성하는데 필요한 값들을 제공해줍니다.

초기 세팅

App.tsx

// 기존 Redux가 있으면 사용하면 안됩니다!
function App(props) {
	...
	return (
		//
		<<ApiProvider api={api}>
			{children}
		</Provider>
	)
}

API.ts

export const api = createApi({
	// 쿼리에 대한 동작 선언
	baseQuery: fetchBaseQuery({ baseUrl: '/' }),
	// 요청이 보내질 장소
	endpoints: (builder) => ({
		// 선언해두면 발동
		getApi: builder.query({
			query: () => '/'
		}),
		// 함수를 호출 시킬 시 발동!
		setApi: builder.mutation({
			// 요청을 보낼 URL
			query: ({id, ...patch}) => ({
					url: `/set/${id}`,
					// Get, Patch, Delete 모두 가능
					method: 'Post' ,
					body: patch
		})
});

// API 호출을 위해 사용!
export const { useGetApiQuery } = api;
// store 세팅을 위해 사용!
export const { reducerPath, reducer, middleware } = api

RootStore.ts

import { reducerPath, reducer, middleware } from './api.ts';
import { middleware2 } from './api2.ts';

export const store = configureStore({
	reducer: {
		// 한개만!!
		[reducerPath]: reducer
	},
	// 사용하는 커스텀, 라이브러리 미들웨어 전부
	middleware: gDM => gDM().concat(middleware, middleware2)
});

// refetch 관련 동작을 사용할 때 선택적으로 사용해요.
setupListeners(store.dispatch);

API 사용하기

사용하는 컴포넌트or페이지.tsx

import { api, useGetApiQuery, usePostApiMutation } = from './api';

export default function App(){
	const { data, isLoading, isFetching, error } = useGetApiQuery();
	// 똑같이 사용가능한 방식이에요
	const { data, isLoading, isFetching, error } = api.endpoints.getApi.useQuery();

	const [mutationApi, mutationData] = usePostApiMutation();
	// 똑같이 사용가능한 방식이에요
	const [mutationApi, mutationData] = api.endpoints.getApi.usePostApiMutation(id);

	...
	const handleClick = async () => {
		await mutationApi(body);
	}

기능

다양한 부분을 지정, 커스텀 가능

const axiosBaseQuery =
  (
    { baseUrl }: { baseUrl: string } = { baseUrl: '' }
  ): BaseQueryFn<
    {
      url: string
      method: AxiosRequestConfig['method']
      data?: AxiosRequestConfig['data']
      params?: AxiosRequestConfig['params']
    },
    unknown,
    unknown
  > =>
  async ({ url, method, data, params }) => {
    try {
      const result = await axios({ url: baseUrl + url, method, data, params })
      return { data: result.data }
    } catch (axiosError) {
      let err = axiosError as AxiosError
      return {
        error: {
          status: err.response?.status,
          data: err.response?.data || err.message,
        },
      }
    }
  }

export const rootApi = createApi({
  //   baseQuery: fetchBaseQuery({ baseUrl: '/' }),
  baseQuery: axiosBaseQuery({ baseUrl: '/' }),
  endpoints: () => ({}),
});

// injectionEndpoints
// 오버라이딩 할지 말지 overrideExisting 옵션을 통해 설정 가능
export const customApi= rootApi.injectEndpoints({
  tagTypes: ['login'],
  endpoints: (build) => ({
    getApi: build.query({
      query: (id) => `/${id}`,
			// API 성공했을 때
			transformResponse
			// 실패 했을 때
			transformErrorResponse
			// key
			providesTags
			// 캐시값을 조절 가능
			onCacheEntryAdded
			// 캐시 유지시간
			keepUnusedDataFor
    }),
    setApi: build.mutation({
			// queryFn : query를 좀 더 자세히 쓸 때
			query: ({id, ...patch}) => ({
						url: `/set/${id}`,
						method: 'Post' ,
						body: patch
			})
			transformResponse
			transformErrorResponse
			// 무효화 시킬 key
			invalidatesTags
			// 캐시 업데이트 방식 지정
			updateQueryData
    }),
  }),
});

// Auto-generated hooks
export const { useGetApiQuery, useSetApiMutation } = keepLoginApi;
// useQuery : 데이터 가져오기
// useQuerySubscription : refetch용이면서 자동 트리거
// useQueryState : 캐쉬된 데이터 읽기
// useLazyQuery : 데이터를 가져오는 시점을 수동으로
// useLazyQuerySubscription : refetch 수동으로 제어 및 캐시 존재시 스킵

// Possible exports
export const { endpoints, reducerPath, reducer, middleware } = keepLoginApi;

// query
const { data, currentData, error, isUninitialized, isLoading, isFetching, isSuccess, isError}
=useQuery(skip, pollingInterval, selectFromResult, refetchOnMountOrArgChange, refetchOnFocus, refetchOnReconnect)
// mutation
const { data, error, isUninitialized, isLoading, isSuccess, isError, reset }
= useMutation(selectFromResult, fixedCacheKey)

Optimistic Update and Pessimistic Update

pessimistic update → 캐쉬 된 데이터를 업데이트하기 전 서버의 응답을 기다림

optimistic update → 일단 업데이트함

💡 modest-hugle-sl98x3 - CodeSandbox

!https://codesandbox.io/csb-ios.svg

updateQueryData RTKquery로 클라이언트에서만 쿼리 데이터를 조작하고 싶다면

유틸

prefetch

데이터 미리 가져오기

dispatch(api.util.prefetch('getPosts', undefined, { force: true }))

updateQueryData

데이터를 업데이트해줌

const patchCollection = dispatch(
  api.util.updateQueryData('getPosts', undefined, (draftPosts) => {
    draftPosts.push({ id: 1, name: 'Teddy' })
  })
)

upsertQueryData

캐시에 업서트하기위한 인공 APi 요청 작업 생성자

await dispatch(
  api.util.upsertQueryData('getPost', { id: 1 }, { id: 1, text: 'Hello!' })
)
Comments