💡 배우게 될 것들
- Redux Toolkit의 RTK Query 데이터 패칭 기능을 어떻게 설정하고 사용하는지
✅ 배우기 위해 필요한 것들
Redux Toolkit Query 튜토리얼에 오신걸 환영합니다! 이 튜토리얼은 Redux Toolkit의 "RTK Query" 데이터 패칭 기능을 소개하고 올바르게 사용하는 방법에 대해서 간단하게 소개합니다.
RTK Query는 웹 애플리케이션에서 데이터를 로딩하는 흔한 케이스를 간단하게하는 진보된 데이터 패칭, 캐싱 툴입니다. RTK Query는 Redux Toolkit core의 위에서 작성되었고, RTK의 API들은 createSlice
와 createAsyncThunk
를 확장해서 만들어졌습니다.
RTK Query는 @reduxjs/toolkit
패키지의 추가적인 애드온으로 포함되어져있습니다. Redux Toolkit을 사용해도 RTK Query Api를 사용하지 않아도 되지만 우리는 RTK Query의 데이터 패칭과 캐싱이 많은 사용자들에게 택을 가져다 줄것이라고 생각합니다
이 튜토리얼에서 우리는 React와 Redux Toolkit을 사용하는 걸 가정하지만, 다른 UI layers들과도 사용할 수 있습니다. 예시들은 애플리케이션 코드가 src
폴더에 있는 전형적인 Create-React-App 폴더 구조를 기반으로 하지만, 패턴들은 대부분의 사용하는 프로젝트나 폴더 구조에 적용할 수 있습니다.
RTK Query가 어떻게 작동하는지 보기위해, 기본적인 사용 예시를 만들어 보겠습니다. 이 예시에서는 React를 사용하고 RTK Query에서 자동 생성된 리액트 hooks를 사용하는 것을 가정하겠습니다.
첫째로, 공개 API인 PokeAPI를 이용해서 서비스 정의를 생성하겠습니다.
// src/services/pokemon.ts
// createApi를 import하기위해 React 엔트리 포인트 사용
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { Pokemon } from './types'
// base URL과 엔드포인트들로 서비스 정의
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query<Pokemon, string>({
query: (name) => `pokemon/${name}`,
}),
}),
})
// 정의된 엔드포인트에서 자동으로 생성된 훅을 함수형 컴포넌트에서 사용하기 위해 export
export const { useGetPokemonByNameQuery } = pokemonApi
RTK Query를 사용할때, 전체 API를 보통 한곳에 정의합니다. 이 점은 swr
이나 react-query
같은 라이브러리들과 가장 많이 다른 점일 것인데, 여기에는 여러가지 이유들이 있습니다. 저희의 관점에서는 여러개의 커스텀 hooks들이 다른 파일들에 있는 것 보다 한곳에 위치하는게 요청, 캐시 무효화, 공통 앱 설정을 관리하기가 더욱 쉽다고 생각합니다.
💡 팁
일반적으로, 애플리케이션에 필요한 베이스 URL당 하나의 API 슬라이스를 가져야 합니다. 예시로 만약 사이트에서/api/posts
와/api/users
에서 데이터를 가져와야 한다면/api
를 베이스 URL로 하는 하나의 API 슬라이스를 만들고posts
와users
로 엔드포인트를 나누어야 합니다. 이러면 endpoints와의 관계를 tag로 정의해서 자동 데이터 리패칭 기능을 효과적으로 활용할 수 있습니다.
유지보수적 관점에서, 하나의 API 슬라이스에 엔드포인트들을 포함하면서 엔드포인트들을 여러개의 파일에 나누어 정의하고 싶을 수도 있습니다. 코드 스플리팅에서 어떻게injectEndpoints
프로퍼티를 사용해서 여러 파일들에서 하나의 API 슬라이스로 API 엔드포인트를 주입할 수 있는지 알아보세요.
RTK Query는 리덕스 루트 리듀서에 추가해야하는 "슬라이스 리듀서"와 데이터 패칭을 위한 커스텀 미들웨어를 생성합니다. 둘 다 리덕스 스토어에 추가해야 합니다.
// src/store.ts
import { configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import { setupListeners } from '@reduxjs/toolkit/query'
import { pokemonApi } from './services/pokemon'
export const store = configureStore({
reducer: {
// 특정 top-level slice에서 생성된 리듀서를 추가
[pokemonApi.reducerPath]: pokemonApi.reducer,
},
// 캐싱, 요청 취소, 폴링 등등 유용한 rtk-query의 기능들을 위한 api 미들웨어 추가
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(pokemonApi.middleware),
})
// 옵셔널, refetchOnFocus/refetchOnReconnect 기능을 위해 필요함
// setupListeners 문서를 참고 - 커스텀을 위한 옵셔널 콜백을 2번째 인자로 받음
setupListeners(store.dispatch)
만약 애플리케이션을 Provider로 감싸지 않았다면, 리액트 애플리케이션 컴퍼넌트를 감싸는 리덕스 스토어의 표준 패턴을 사용하세요:
// src/index.tsx
import * as React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './app/store'
const rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
서비스를 정의하면 hooks를 가져와서 요청을 생성할 수 있습니다.
// src/App.tsx
import * as React from 'react'
import { useGetPokemonByNameQuery } from './services/pokemon'
export default function App() {
// 자동으로 데이터를 패치하고 쿼리 값을 가져오는 쿼리 hook을 사용
const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
// 각각의 hooks은 생성된 엔드포인트에서도 접근 가능함
// const { data, error, isLoading } = pokemonApi.endpoints.getPokemonByName.useQuery('bulbasaur')
return (
<div className="App">
{error ? (
<>Oh no, there was an error</>
) : isLoading ? (
<>Loading...</>
) : data ? (
<>
<h3>{data.species.name}</h3>
<img src={data.sprites.front_shiny} alt={data.species.name} />
</>
) : null}
</div>
)
}
요청을 생성할 때 여러 방법으로 상태를 추적할 수 있습니다. data
, status
, error
로 알맞는 UI를 렌더링할 수 있습니다. 또한 useQuery
는 유틸리티 불리언 값인 isLoading
, isFetching
, isSuccess
, isError
로 가장 최근의 요청에 대한 값을 제공합니다.
흥미롭네요... 하지만 만약 여러개의 포켓몬을 한번에 보여주고 싶거나 여러개의 컴포넌트들이 같은 포켓몬을 불러오면 어쩌죠?
RTK Query는 어떤 컴포넌트든 같은 쿼리를 구독하면 항상 같은 데이터를 사용할 수 있도록 보장합니다. RTK Query는 자동으로 중복 요청을 제거하기 때문에 진행중인 요청과 성능 최적화에 대해서 걱정할 필요가 없습니다. 아래의 샌드박스를 시작해보죠. 브라우저 데브툴의 네트워크 탭을 확인해보세요. 4개의 구독된 컴포넌트가 있음에도 3개의 요청을 볼 수 있습니다. bulbasur
는 오직 하나만 요청하고 두개의 컴포넌트의 로딩 상태는 동기화되어있습니다. 재미를 위해, 드롭다운의 값을 Off
에서 1s
로 바꿔서 쿼리가 리렌더링할때 행동을 봅시다.