🥯 애피타이저
RTK Query란?
RTK Query는 웹 애플리케이션에서 데이터를 가져오는 상황을 간단하게 하여 data fetching과 cashing 로직을 직접 작성할 필요가 없도록 하는 강력한 data fetching, cashing 툴입니다.
RTK Query는 Redux Toolkit를 설치하면 내장되어 있기 때문에 따로 설치를 하지 않아도 선택하여 사용할 수 있습니다.
RTK Query를 왜 사용하나요?
- API 호출 및 데이터 관리와 관련한 boilerplate 코드를 대부분 처리가 가능하므로 더 적은 코드를 작성할 수 있습니다.
- 자체적으로 캐싱을 처리하고 불필요한 요청을 줄여서 애플리케이션의 성능을 향상합니다.
또한 백그라운드에서 백엔드 데이터를 프리로드하여 애플리케이션 로딩 속도를 빠르게 만들 수도 있습니다. - REST 및 GraphQL 등의 다양한 API 유형을 지원하며, 커스텀 로직을 추가하여 서버에서 데이터를 처리하는 방법을 확장할 수 있습니다.
- 타입스크립트에 최적화되어 있습니다. ( 전 자바스크립트를 사용하여 블로깅했읍니다.. )
🍖 메인
createApi()
createApi는 RTK Query의 핵심입니다. 데이터를 가져오고 변경하는 방법을 포함해서 백엔드 API 및 기타 비동기 소스에서 데이터를 검색하는 방법을 설명하는 endpoint를 정의할 수 있습니다.
대부분의 경우에는 base URL 당 하나의 API Slice를 사용해야 합니다.
reducerPath
store에서 서비스가 마운트될 고유 키입니다.
Redux Toolkit의 createSlice에서 이름을 설정하는 것과 비슷합니다.
기본값은 api입니다.
baseQuery
모든 endpoint에 사용할 기본 쿼리를 정의하는 함수입니다. 기본 헤더, 인증 및 기타 일반적인 설정을 제공하는 데 사용할 수 있습니다.
일반적으로는 fetchBaseQuery를 사용하여 baseURL을 포함한 다른 옵션들을 설정할 수 있습니다.
fetchBaseQuery()는 HTTP 요청을 간소화하는 것을 목표로 하는 아주 가벼운 fetch wrapper입니다.
axios, superagent 또는 다른 무거운 라이브러리를 완전히 대체할 수는 없지만, 대부분의 HTTP 요청을 처리할 수 있습니다.
fetchBaseQuery는 fetch의 RequestInit 인터페이스의 모든 표준 옵션과 baseUrl, prepareHeaders, paramsSerializer, timeout 등을 가져옵니다.
- baseUrl
"https://api.themoviedb.org/3/"와 같은 모양으로 할당합니다.
baseUrl을 할당하지 않으면 기본적으로 요청이 이루어지는 위치의 상대 경로가 사용됩니다. 항상 이 값을 할당하는 것이 좋습니다. - prepareHeaders
모든 요청에 헤더를 삽입할 수 있습니다.
endpoint에서 헤더를 지정할 수 있지만, 일반적으로 인증과 같은 일반적인 헤더를 여기에 설정하는 것이 좋습니다. - paramsSerializer
params에 전달된 데이터에 사용자 정의 변환을 적용하는 데 사용할 수 있는 함수입니다. 이 함수를 제공하지 않으면 매개변수가 새 URLSearchParams()에 직접 전달됩니다. - timeout
요청이 시간 초과되기 전까지 걸릴 수 있는 최대 시간을 나타내는 ms 단위의 숫자입니다.
endpoints
서버에 대해 수행하려는 작업의 모음입니다.
사용자가 원하는 이름 별로 객체를 생성하고, 각 객체는 요청 유형과 해당 요청에 대한 설정 객체를 포함합니다.
builder 구문을 사용하여 정의합니다.
builder 객체는 query, mutation, subscription 메서드를 포함하고, 각 메서드는 builder.query, builder.mutation, builder.subscription으로 호출할 수 있습니다.
각 메서드들은 endpoint 객체를 생성하고, endpoint 객체의 API 요청을 정의하는 콜백 함수를 반환합니다.
이 콜백 함수는 endpoint의 동작을 정의하며, fetchBaseQuery 함수와 함께 사용됩니다.
builder.query는 데이터를 가져오는 요청에 대한 쿼리 매개 변수를 설정하는 데에 사용됩니다.
이 메서드를 사용하여 쿼리 문자열, 필터, 페이지 및 정렬 매개 변수를 설정할 수 있습니다.
builder.mutation는 데이터를 POST, PUT, PATCH, DELETE와 같은 HTTP 메서드를 사용하여 보낼 수 있고, 요청의 URL, Body, Header 및 기타 옵션을 구성할 수 있습니다.
builder.subscription는 반환된 콜백 함수에서 실시간으로 데이터를 수신하기 위한 구독을 만들고, 구독이 통지될 때마다 fulfilled 액션을 보냅니다.
이 메서드를 사용하면 실시간으로 데이터를 받아올 수 있기 때문에, WebSocket이나 Server-Sent Events 등과 같은 실시간 통신 방법을 통해 데이터를 전송하는 서버에서 유용합니다.
tagTypes
엔드포인트에서 사용하는 태그를 정의하는 객체입니다.
선택적으로 사용하는 옵션이지만, 사용한다면 캐싱 및 무효화에 사용할 수 있도록 정의해야 합니다.
endpoints에서 provideTags로 제공한 다음, invalidatesTags로 무효화할 수 있습니다.
keepUnusedDataFor
사용하지 않은 데이터를 캐시에 보관할 기간(초)입니다. 불필요한 데이터 다시 가져오기를 방지하는 데 사용할 수 있습니다.
refetchOnMountOrArgChange
캐시된캐시 된 결과를 이미 사용할 수 있는 경우에서 RTK 쿼리가 캐시 된 결과만 제공할지, true로 설정할 때 또는 마지막 쿼리 성공 결과 이후 충분한 시간이 경과한 경우 다시 가져올지 제어할 수 있습니다. 기본값은 false입니다.
refetchOnMountOrArgChange | |
false | 아직 존재하지 않는 한 쿼리를 수행하지 않습니다. |
true | 동일한 캐시에 대한 마지막 쿼리 이후 충분한 시간이 경과한 경우 쿼리를 강제로 refetch할 수 있습니다. |
number | 동일한 캐시에 대한 마지막 쿼리 이후 충분한 시간이 경과한 경우 쿼리를 강제로 refetch할 수 있습니다. 값은 초 단위입니다. |
refetchOnFocus
브라우저 창에 포커스를 다시 가질 때 쿼리를 강제로 refetch할 수 있습니다. 기본값은 false입니다.
refetchOnReconnect
네트워크가 다시 연결되었을 때 쿼리를 강제로 refetch할 수 있습니다. 기본값은 false입니다.
<ApiProvider />
Redux를 사용하지 않을 때 Provider를 대체합니다.
setupListeners()
refetchOnMount와 refetcOnReconnect 기능을 위해 사용합니다.
createApi import 하기
import { createApi } from '@reduxjs/toolkit/query'
import { createApi } from '@reduxjs/toolkit/query/react'
두 가지 import 방법으로 RTK Query를 시작할 수 있습니다.
@reduxjs/toolkit/query 패키지는 React와 관련이 없는 일반 JS 및 TS 애플리케이션에서 사용할 수 있는 RTK Query API를 제공합니다.
이 패키지를 사용하면 createApi 함수와 함께 직접 Redux store를 조작하거나, React 컴포넌트 외부에서 RTK Query를 사용할 수 있습니다.
@reduxjs/toolkit/query/react 패키지는 React 애플리케이션에서 RTK Query를 사용하기 위한 도구를 제공합니다.
이 패키지를 사용하면 useLazyQuery, useMutation, useQuery 등의 hooks를 제공하며, React 컴포넌트 내부에서 간편하게 RTK Query를 사용할 수 있습니다.
API Slice를 생성하기
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const movieApi = createApi({
reducerPath: "movieApi",
baseQuery: fetchBaseQuery({
baseUrl: "https://api.themoviedb.org/3/",
}),
endpoints: (builder) => ({
getTopRatedMovies: builder.query({
query: (arg) => {
const { api_key, language } = arg;
return {
url: `movie/top_rated`,
params: { api_key, language },
};
},
}),
getPopularMovies: builder.query({
query: (arg) => {
const { api_key, language } = arg;
return {
url: `movie/popular`,
params: { api_key, language },
};
},
}),
}),
});
// 자동으로 생성되는 훅을 사용하기 위해서 export 합니다.
export const { useGetTopRatedMoviesQuery, useGetPopularMoviesQuery }
= movieApi;
예제에서는 데이터를 받아오는 GET 요청만을 사용했기 때문에, builder.query 만을 사용했습니다.
builder.query의 query 프로퍼티에서 요청이 실행될 때 필요한 쿼리 매개 변수를 지정합니다.
query 함수에서 인수를 arg로 받고, arg 객체에는 kind, api key, language가 있습니다.
kind는 top_rated 또는 popular가 될 문자열, api key는 저의 TMDB API KEY, language는 영화 데이터의 언어를 설정합니다.
return 문은 요청의 url과 쿼리 매개 변수를 구성합니다.
url은 API의 endpoint를 설정하고, params는 쿼리 매개 변수를 설정합니다.
api key와 language를 params에 할당하면 이 params를 url 파라미터로 붙여서 데이터 요청을 보냅니다.
코드의 가장 하단의 useGetMoviesQuery는 자동적으로 만들어진 Hooks입니다.
이 훅은 각각 getMovies를 실행합니다.
Store 설정하기
import { configureStore } from "@reduxjs/toolkit";
import { movieApi } from "../movieApi";
const store = configureStore({
reducer: {
[movieApi.reducerPath]: movieApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(movieApi.middleware),
});
export default store;
API Slice에서 추가한 reducer와 RTK Query의 유용한 기능을 사용하기 위한 미들웨어를 store에 추가합니다.
Provider로 컴포넌트 감싸기
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { Provider } from "react-redux";
import store from "./store/index.js";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
Redux를 사용하고 있다면 기존에 사용하던 것처럼 Provider를 사용하여 감싸주고 store를 props로 전달해 주면 됩니다.
만약 store가 없다면 AppProvider를 사용하면 됩니다.
컴포넌트에서 useQuery Hook 사용하기
// TopRatedMovies 컴포넌트
import { useEffect, useState } from "react";
import { useGetTopRatedMoviesQuery } from "../movieApi";
const TopRatedMovies = () => {
const API_KEY = "본인의 API KEY";
const { data, isLoading, isError } = useGetTopRatedMoviesQuery({
api_key: API_KEY,
language: "ko-KR",
});
const [showLoading, setShowLoading] = useState(true);
useEffect(() => { // 쉽게 보기 위하여 타이머를 설정
const timeoutId = setTimeout(() => {
setShowLoading(false);
}, 1000);
return () => {
clearTimeout(timeoutId);
};
}, []);
if (isLoading || showLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Error...</div>;
}
return (
<div>
<h1>Top Rated Movies</h1>
<ul>
{data.results.map((movie) => (
<li key={movie.id}>
<div>{movie.title}</div>
<img
src={`https://image.tmdb.org/t/p/w200${movie.poster_path}`}
alt={movie.title}
/>
<img
src={`https://image.tmdb.org/t/p/w400${movie.backdrop_path}`}
alt={movie.title}
/>
<div>{movie.overview}</div>
</li>
))}
</ul>
</div>
);
};
export default TopRatedMovies;
useGetTopRatedMoviesQuery hook은 endpoints에서 builder.query로 정의했던 getTopRatedMovies를 담은 endpoint hook입니다. 이 hook은 2개의 파라미터 queryArg와 queryOptions를 선택적으로 가집니다.
queryArg 파라미터는 query 콜백에 전달되어 url를 생성합니다.
예제에서는 api key와 language를 전달합니다.
queryOptions 객체는 데이터 요청을 제어하는 데에 사용되는 추가적인 파라미터를 받습니다.
queryOptions 객체의 파라미터 | |
skip | 해당 렌더에 대해 실행 중인 쿼리를 skip할 수 있습니다. 기본값은 false입니다. |
pollingInterval | 지정한 간격(ms)에 따라 자동으로 쿼리를 refetch 할 수 있습니다. 기본값은 0입니다. |
selectFromResult | 훅에서 반환되는 결괏값을 변경하고, 변경된 결과값을 렌더에 최적화할 수 있습니다. |
refetchOnMountOrArgChange | true인 경우, 마운트시 항상 쿼리를 강제로 refetch 할 수 있습니다. number인 경우, 동일한 캐시에 대한 마지막 쿼리 이후 충분한 시간이 경과한 경우 쿼리를 강제로 refetch할 수 있습니다. 기본값은 false입니다. |
refetchOnFocus | 브라우저 창에 포커스를 다시 가질 때 쿼리를 강제로 refetch할 수 있습니다. 기본값은 false입니다. |
refetchOnReconnect | 네트워크가 다시 연결되었을 때 쿼리를 강제로 refetch할 수 있습니다. 기본값은 false입니다. |
자주 사용되는 useQuery Hook의 반환값
useQuery Hook의 반환값 | |
data | 반환된 데이터의 결괏값입니다. |
error | 에러 결괏값입니다. |
isUninitialized | true일 때 쿼리가 아직 시작하지 않았음을 나타냅니다. |
isLoading | true일 때 쿼리가 처음 로딩 중이고 아직 데이터가 없다는 것을 나타냅니다. 이는 처음 요청에만 해당됩니다. |
isFetching | true일 때 쿼리가 현재 fetching 중이지만 이전 요청의 데이터가 있을 수 있음을 나타냅니다. 이는 처음 요청과 이후 요청 모두에 해당합니다. |
isSuccess | true일 때 쿼리의 요청이 성공했고 데이터가 있다는 것을 나타냅니다. |
isError | true일 때 쿼리의 error 상태를 나타냅니다. |
refetch | 쿼리를 강제로 refetch 시키는 함수입니다. |
대부분의 상황에서는 UI를 렌더링 하기 위해서 data, isLoading or isFetching이면 충분합니다.
isLoading과 isFetching 간단비교
isLoading은 서버에 데이터 요청을 처음 할 때
isFetching은 서버에 데이터 요청을 다시 할 때 ( 캐시 된 데이터가 있을 때 )
App.js
import { useState } from "react";
import "./App.css";
import TopRatedMovies from "./component/TopRatedMovies";
import PopularMovies from "./component/PopularMovies";
function App() {
const [topRated, setTopRated] = useState(true);
const [popular, setPopular] = useState(false);
return (
<>
<button
onClick={() => {
setTopRated(true);
setPopular(false);
}}
>
{"Top Rated Movies"}
</button>
<button
onClick={() => {
setTopRated(false);
setPopular(true);
}}
>
{"Popular Movies"}
</button>
{topRated && <TopRatedMovies />}
{popular && <PopularMovies />}
</>
);
}
export default App;
이 코드는 원하는 영화 필터 버튼을 누를 때마다 데이터를 요청하여 영화 데이터를 받아와 화면에 보여줍니다.
같은 버튼을 계속 클릭한다면?
이 예제에서는 버튼을 클릭하면 데이터를 요청합니다. 그리고 일정 시간 동안 Loading... 이 보이고 시간이 지나면 데이터가 화면에 보이게 됩니다.
만약 같은 버튼을 연속해서 클릭한다면 같은 데이터임에도 불구하고 계속 데이터를 요청하고 컴포넌트가 매번 렌더링 될 것입니다.
따라서 불필요한 요청과 업데이트가 반복되어 사용자 경험이 매우 떨어질 것입니다.
하지만 예제에서는 useQuery Hook을 사용하여 데이터를 요청하기 때문에, 서버에서 데이터가 변경되지 않았다면 이전 데이터가 캐시에 남아 있기 때문에 새로운 데이터를 요청하지 않고, 새로 렌더링 하지도 않습니다.
따라서 매우 효율적입니다.
🍨 디저트
부족하고 또 부족한 글 읽어주셔서 감사합니다.
그래도 다음에 또 오세요.
왜냐하면 내용이 계속 추가될 수 있거든요.
레퍼런스
https://junsangyu.gitbook.io/rtk-query/tutorial
https://ko.redux.js.org/tutorials/essentials/part-7-rtk-query-basics/
'React' 카테고리의 다른 글
[React] 리렌더링에 대한 고민 (2) | 2023.09.17 |
---|---|
Redux를 간단하게 알아보자 (0) | 2023.04.24 |
React 간단 정리 (5) | 2023.03.22 |