RTK Query공식문서에 의하면 RTK Qurey는 다음과 같은 기능을 수행합니다.
RTK Query는 강력한 data fetching, caching 툴입니다. 웹 애플리케이션에서 데이터를 가져오는 단순한 상황을 간단하게 만들어서 data fetching과 caching 로직을 스스로 작성할 필요가 없도록 만들어졌습니다.
redux toolkit 에는 createAsyncThunk
를 사용해서, thunk 를 만들어서 async 요청을 관리할 수 있도록 하는 기능이 있습니다. 하지만 createAsyncThunk
의 경우 async 요청의 응답을 따로 createSlice 를 활용해서 관리해줘야 해서, 코드가 늘어날 수 밖에 없습니다.
이를 보완해 만들어진 것이 바로 RTK query
로 createSlice 와 createAsyncThunk 의 기능을 동시에 사용하면서, react-query
와 유사하게 백엔드에서 넘어온 데이터를 관리할 수 있습니다.
특히 RTK Query 는 캐싱
관련 기능도 지원하기 때문에, 불필요한 요청을 줄일 수도 있으며 post요청 후 자동으로 get하는 기능을 구현할 수 있기에 보다 적은 코드로 필요한 로직을 작성할 수 있습니다.
뿐만 아니라, 프론트 데이터와 api 데이터 관리하는 로직 자체를 분리시킬 수도 있습니다.
백엔드와의 요청을 통해서 받아오는 데이터 → RTK Query 로 관리
프론트엔드만의 데이터 → createSlice 로 관리
$ npm i @reduxjs/toolkit react-redux
api 는 createApi
라는 함수를 사용해서 만들 수 있습니다. 해당 함수 안에 baseUrl과 관련한 요청을 모두 작성하는 방식입니다.
src > api > postApi.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api이름 = createApi({
reducerPath: '리듀서이름',
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_URL,
}),
endpoints: (builder) => ({
요청이름: builder.query({
query: (주소에 넘길 값) => 'api 주소값/(주소에 넘길 값)',
}),
}),
});
export const {
use요청이름Query
} = postApi;
endpoints
에 보내고자 하는 요청 이름을 명시하면, 자동으로 use요청이름Query
를 사용할 수 있게 됩니다.
요청을 명시할 때 builder.query
라고 작성하게 되면, get 요청
을 보낼 수 있게 되고, builder.mutation
이라고 작성하면, post 혹은 put 요청
등을 보낼 수 있게 됩니다.
이렇게 생성된 api슬라이스를 컴포넌트에서 호출을 해주면 자동으로 isLoading, isError, data 를 반환해줍니다.
const { data: posts, isLoading, isError } = useGetPostsQuery();
RTK Qurey의 API
createApi()
: RTK Query 기능의 코어.
- 데이터를 패치하고 변환하는 설정을 포함해서 엔드포인트들에서 어떻게 데이터를 패치하는지 정의할 수 있습니다.
- 대부분의 케이스에서는 베이스 URL당 하나의 API 슬라이스를 사용해야 합니다.
fetchBaseQuery()
간단한 요청을 위한 fetch의 래퍼.
- 대부분의 사용자에게 createApi의 baseQuery로 권장합니다.
src> api > postApi.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const postApi = createApi({
reducerPath: 'postApi',
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_URL,
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => 'posts',
}),
}),
});
export const {
useGetPostsQuery
} = postApi;
store>index.js
import { configureStore } from '@reduxjs/toolkit';
import { postApi } from '../api/postApi';
import { setupListeners } from '@reduxjs/toolkit/query';
export const store = configureStore({
reducer: {
[postApi.reducerPath]: postApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(postApi.middleware),
});
setupListeners(store.dispatch);
RTK Qurey의 API
setupListeners()
: 서비스 내에서 사용자의 행동이나 요소의 변화 등을 지켜보다가, 요청을 보낼 수 있도록 listener 를 준비해놓기 위한 함수입니다.
components/Posts.jsx
import React from 'react';
import { useGetPostsQuery } from '../api/postApi';
function Posts() => {
const { data: posts, isLoading, isError } = useGetPostsQuery();
if (isLoading) {
return <div>로딩 중...</div>
}
if (isError || !posts) {
return <div>오류 발생</div>;
}
return (
<div>
{posts.map((post) => (
<>
<p>{post.title}</p>
<p>{post.body}</p>
</>
))}
</div>
);
};
export default Posts;
import { Provider } from 'react-redux';
import Posts from './components/Posts';
import {store} from './store/index';
function App() {
return (
<Provider store={store}>
<Posts/>
</Provider>
);
}
export default App;
RTK Qurey의 API
<ApiProvider />
: Redux 스토어를 가지고 있지 않다면Provider
로 사용 가능합니다.
api/postApi.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const postApi = createApi({
reducerPath: 'postApi',
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_URL,
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => 'posts',
}),
getPostById: builder.query({
query: (postId) => `posts/${postId}`,
}),
createPost: builder.mutation({
query: ({ data }) => ({
url: 'posts',
method: 'POST',
body: data,
}),
}),
}),
});
export const {
useGetPostsQuery,
useGetPostByIdQuery,
useCreatePostMutation,
} = postApi;
components/Post.jsx
import React from 'react';
import { useGetPostByIdQuery } from '../api/postApi';
function Post({ postId }) {
const { data: post, isLoading, isError } = useGetPostByIdQuery(postId);
if (isLoading) {
return <div>로딩 중...</div>
}
if (isError || !post) {
return <div>Something went wrong</div>;
}
return (
<div>
<p>{post.title}</p>
<p>{post.body}</p>
</div>
);
};
export default Post;
app.js
import { Provider } from 'react-redux';
import Post from './components/Post';
import Posts from './components/Posts';
import {store} from './store/index';
function App() {
return (
<Provider store={store}>
<Posts/>
<Post postId={1}/>
</Provider>
);
}
export default App;
components/PostInput.jsx
import React, { useState } from 'react';
import { useCreatePostMutation } from '../api/postApi';
function PostInput() {
const [postInput, setPostInput] = useState({title: '', body: ''});
const [createPost] = useCreatePostMutation();
const inputChangeHandler = (e) => {
const { name, value } = e.target
setPostInput({...postInput, [name]:value});
};
const submitHandler = () => {
createPost({
data: postInput
})
};
return (
<div>
<input name="title" onChange={inputChangeHandler} />
<input name="body" onChange={inputChangeHandler} />
<button onClick={submitHandler}>
Save
</button>
</div>
);
};
export default PostInput;
app.js
import { Provider } from 'react-redux';
import Post from './components/Post';
import PostInput from './components/PostInput';
import Posts from './components/Posts';
import {store} from './store/index';
function App() {
return (
<Provider store={store}>
<PostInput/>
<Posts/>
<Post postId={1}/>
</Provider>
);
}
export default App;
Tag를 활용하면 각 요청별로 post후 자동으로 get요청을 하는 로직을 만들수 있습니다.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const postApi = createApi({
reducerPath: 'postApi',
tagTypes: ['Posts'],
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_URL,
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => 'posts',
providesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPostById: builder.query({
query: (postId) => `posts/${postId}`,
providesTags: (result, error, postId) => [{ type: 'Posts', id: postId }],
}),
createPost: builder.mutation({
query: ({ data }) => ({
url: 'posts',
method: 'POST',
body: data,
}),
invalidatesTags: (result) =>
result
? [
{ type: 'Posts', id: 'LIST' },
]
: [],
}),
}),
});
export const {
useGetPostsQuery,
useGetPostByIdQuery,
useCreatePostMutation,
} = postApi;
{ type: 'Posts', id: 'LIST' }
라는 태그를 가진 요청인 getPosts 요청을 다시 보내게 됩니다.