redux-thunk, redux toolkit

비동기 요청을 보내는 미들웨어 (Axios)

1) redux-thunk의 개념

리덕스에서 비동기 작업을 처리할수 있또록 도와주는 미들웨어.
해당 미들웨어를사용하면 액션 객체가 아니라 함수 그 자체를 디스패치 할수 있도록 해준다.

기존에는 아래와 같이 액셩 생성함수를 작성했었는데 axios 요청을 보내는 액션을 만들때는

action must be plain objects. Use custom middleware for async actions

같은 오류가 나온다. 즉 액션 생성함수를 작성할때는 async,await를 못쓴다는 얘기

이를 위해서 redux thunk는 액션이 액션 객체를 반환하는 것이 아니라, 아래처럼 함수를 반환할수 있도록 해준다.

export const fetchData = () => {
  return async function(dispatch, getState) {
	  const response = await axios.get('url')
	  return {
	    type: 'FETCH_DATA',
	    payload: response.data
	  }
  }
}
  • dispatch를 하게되면
  • 해당 액션을 반환하는 함수를 우선 실행하고,
  • 함수 실행을 통해서 요청이 다 이루어져서 전달할 데이터가 만들어지고 나면,
  • reducer가 해당 데이터가 포함된 객체가 넘어가게 되는것

2) redux-thunk 설치 및 세팅

yarn add redux thunk

적용하는 코드는 아래와 같다.

import ReduxThunk from 'redux-thunk';

const store = createStore(rootReducer, applyMiddleware(ReduxThunk));

나는 logger,devtools랑 같이 쓰고 싶다?

import { applyMiddleware, compose, legacy_createStore as createStore } from "redux";
import ReduxThunk from 'redux-thunk';
import logger from 'redux-logger';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(ReduxThunk, logger)));

3) 요청 보내고 , 데이터 받아서 관리하기 : redux 작성

https://jsonplaceholder.typicode.com/posts

위의 주소로 GET 요청을 보내고 데이터를 받아서 관리하는 과정을 redux를 통해서 진행하기로 했다.

ducks 방식으로 파일 하나에 리덕스 관련 메소드를 작성한다.

import axios from 'axios'

// 액션 타입

const GET_POSTS = 'posts/GET_POSTS'
const GET_POSTS_SUCCESS = 'posts/GET_POSTS_SUCCESS'
const GET_POSTS_ERROR = 'posts/GET_POSTS_ERROR'

// 액션 객체 넣기

export const getPosts = () => async (dispatch, getState) => {
    dispatch({ type: GET_POSTS }) // 요청 시작
    try {
        const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
        dispatch({
            type: GET_POSTS_SUCCESS,
            payload: { posts: response.data },
        }) // 요청 성공
    } catch (e) {
        dispatch({ type: GET_POSTS_ERROR, error: e }) // 요청 실패
    }
}

// 초기값

const initialState = {
    loading: false,
    data: null,
    error: null,
}

// 추가 / 제거

export default function posts(state = initialState, action) {
    switch (action.type) {
        case GET_POSTS:
            return {
                ...state,
                loading: true,
                error: null,
            }
        case GET_POSTS_SUCCESS:
            return {
                ...state,
                loading: false,
                data: action.payload.posts,
            }
        case GET_POSTS_ERROR:
            return {
                ...state,
                loading: false,
                error: action.error,
            }
        default:
            return state
    }
}

액션 타입 지정에서 경로('posts/')를 추가하는 이유는 만약 다른 리듀서를 작성할때 같은 액션타입이 존재한다면 혼동이 생길수 있으므로, 이를 구분하기 위해서 작성한다.

GET_POSTS → 로딩을 True 로 설정하는 액션

GET_POSTS_SUCCESS → 요청이 성공하면, 로딩을 False 로, data 부분을 응답으로 설정하는 액션

GET_POSTS_ERROR → 요청이 실패하면, 에러를 저장하는 액션

액션 생성함수가 아니라 아예 액션 요청과 동시에 distpatch까지 진행하는 코드를 작성했다.

-> 요청이 성공해서 응답이 오면 , 데이터를 reducer 쪽으로 던지는 것이다.

4) 요청 보내고, 데이터 받아서 관리하기: container-presenter 작성

PostContainer는 아래처러 작성한다.

src/pages/Posts/PostsContainer.jsx

import { useSelector, useDispatch } from 'react-redux'
import { Loading } from './../../../../seventhApp/src/components/Common/Loading/style'
function PostsContainer() {
    const { data, loading, error } = useSelector((state) => state.posts)
    const dispatch = useDispatch()

    // 글 가져오는 과정 수행!
    useEffect(() => {
        dispatch(getPosts())
    }, [dispatch])

    if (loading) return <Loading />
    if (error) return <div>에러발생</div>
    if (!data) return null
    return <PostsPresenter posts={data} />
}

export default PostsContainer
  • useEffect를 통해서 postpresenter를 표시하기 이전에 데이터를 가져오는 액션을 진행한다.
  • data 라는 변수에 글이 담겼으니 ,해당 글들을 postspresenter로 props 형태로 넘긴다.

pages/Posts/PostsPresenter.jsx


```jsx
import React from 'react'

function PostsPresenter({ posts }) {
    return (
        <div>
            {posts.map((post) => (
                <p>{post.title}</p>
            ))}
        </div>
    )
}

export default PostsPresenter

이제 Provider를 사용해서 main.jsx에 덮어주고 App.jsx 에는 PostsContainer를 리턴하면 된다.
main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App'
import store from './modules'

ReactDOM.createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <React.StrictMode>
            <App />
        </React.StrictMode>
    </Provider>,
)

App.jsx

import PostsContainer from './pages/Posts/PostsContainer'

function App() {

    return (
        <div className="App">
            <PostsContainer />
        </div>
    )
}

export default App

Redux Toolkit

1) Redux Toolkit

기존에 Redux 외에 설치해야 했던 각종 패키지 기능을 통합해서, 조금 더 간편하게 Redux 기능을 사용할 수 있도록 도와주는 모듈이다.

기본적으로 비동기 작업을 위한 thunk,saga 관련 기능이 내장되어있고, 액션을 간편하게 명시하기위한 redux-actions 관련 기능 또한 내장되어있다.

요약하자면 쓰는 이유는

  • 코드 간소화
  • 패키지 간소화

yarn add @reduxjs/toolkit react-redux

logging 기능 쓰고 싶으면

yarn add redux-logger

2) slice 만들기

action, reducer 각각 작성하는거 귀찮으면 createSlice라는 함수가 존재한다.

export const 슬라이스 이름 = createSlice({
  name: '리듀서 이름',
  초기상태 값,
  reducers: {
    액션이름: (state, action) => {
      액션 dispatch 시 실행될 코드
    },
  },
});

이렇게 해주면 아래와 같이 .actions 혹은 .reducers 를 통해서 꺼내올수 있게 된다.

슬라이스 이름.actions
슬라이스 이름.reducers

store/slices/counterSlice.js

import { createSlice } from '@reduxjs/toolkit'
import { useDispatch, useSelector } from 'react-redux'

const initialState = {
    value: 0,
}

export const counterSlice = createSlice({
    name: 'counter',
    initialState,
    reducers: {
        increment: (state) => {
            state.value += 1
        },
        decrement: (state) => {
            state.value -= 1
        },
    },
})

export function useCounter() {
    const count = useSelector((state) => state.counter.value)
    const dispatch = useDispatch()

    return {
        count,
        dispatch,
    }
}

export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer

이제 컴포넌트에서 불러오는 액션의 위치만 변경해주면 된다.

store/index.js

import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { createLogger } from 'redux-logger';
import counterReducer from './slices/counterSlice';

const logger = createLogger();

const rootReducer = combineReducers({
  counter: counterReducer,
});

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
});

export default store;

components/Counter.jsx

import { increment, useCounter } from '../store/slices/counterSlice';

export default function Counter() {
  const { count, dispatch } = useCounter();

  return (
    <div>
        <p>{count}</p>
        <button onClick={() => dispatch(increment())}>
        +1
        </button>
    </div>
  );
}

3) 기능 추가

import { createSlice } from '@reduxjs/toolkit';
import { useDispatch, useSelector } from 'react-redux';

const initialState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value = state.value + 1;
    },
    decrement: (state) => {
      state.value = state.value - 1;
    },
    incrementByAmount: (state, action) => {
      state.value = state.value + action.payload.diff;
    },
    decrementByAmount: (state, action) => {
      state.value = state.value - action.payload.diff;
    },
  },
});

export const { increment, decrement, incrementByAmount, decrementByAmount } = counterSlice.actions;

export function useCounter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return {
    count,
    dispatch,
  };
}

export default counterSlice.reducer;

components/Counter.jsx

import { decrement, decrementByAmount, increment, incrementByAmount, useCounter } from '../store/slices/counterSlice';

export default function Counter() {
  const { count, dispatch } = useCounter();

  return (
    <div>
        <p>{count}</p>
        <button onClick={() => dispatch(increment())}>
        +1
        </button>
        <button onClick={() => dispatch(decrement())}>
        -1
        </button>
        <button onClick={() => dispatch(incrementByAmount({diff: 2}))}>
        +2
        </button>
        <button onClick={() => dispatch(decrementByAmount({diff: 2}))}>
        -2
        </button>
    </div>
  );
}

Create Async Thunk

1) createAsyncThunk 사용하기

redux toolkit 에는 createAsyncThunk 를 사용해서, thunk 를 만들어서 async 요청을 관리할 수 있도록 하는 기능이 있다.

사용하는 방법은 기존에 slice 를 이용하는 방법과 크게 다르지 않다. 방금은 증가, 감소라는 action type 에 대해서 state 가 어떻게 달라질지를 지정했다면, 지금은

  • 요청을 보낼 때 (pending)
  • 요청이 성공했을 때 (fulfilled)
  • 요청이 실패했을 때 (rejected)

각각에 대해서 state 가 어떻게 달라져야할지를 지정해주면 된다.

이전에 만들었던 redux thunk 예제를 slice를 사용해서 만들어보자

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'

const initialState = {
    entities: [],
    loading: false,
}

export const getPosts = createAsyncThunk('posts/getPosts', async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts')
    return await response.json()
})

export const postSlice = createSlice({
    name: 'posts',
    initialState,
    reducers: {},
    extraReducers: {
        //요청 시작
        [getPosts.pending]: (state) => {
            state.loading = true
        },
        //요청 성공
        [getPosts.fulfilled]: (state, action) => {
            state.loading = false
            state.entities = action.payload
        },
        // 요청 실패
        [getPosts.rejected]: (state) => {
            state.loading = false
        },
    },
})

export default postSlice.reducer

store 에도 postReducer를 추가해준다.
store/index.js

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { createLogger } from "redux-logger";
import counterReducer from "./slices/counterSlice";
import postReducer from "./slices/postSlice";

const logger = createLogger();

const rootReducer = combineReducers({
  counter: counterReducer,
  posts: postReducer,
});

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
});

export default store;

2) createAsyncThunk 사용해보기

PostList 컴포넌트를 만들고 아래과 같이 useDispatch와 useSelector를 사용한다.

import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../store/slices/postSlice";

function PostList() {
  const dispatch = useDispatch();
  const { entities: posts, loading } = useSelector((state) => state.posts);

  useEffect(() => {
    dispatch(getPosts());
  }, []);

  if (loading) return <p>Loading...</p>;
  return (
    <div>
      {posts.map((post) => (
        <p key={post.id}>{post.title}</p>
      ))}
    </div>
  );
}

export default PostList;

RTK Query

1) RTK Query 개념 및 설치

createAsyncThunk 의 경우 async 요청의 응답을 따로 createSlice 를 활용해서 관리해줘야 해서, 코드가 늘어날 수 밖에 없다.

그래서 RTK query 라고 해서, createSlice 와 createAsyncThunk 의 기능을 동시에 사용하면서, react-query 와 유사하게 백엔드에서 넘어온 데이터를 관리할 수 있는 기능이 존재한다.

특히나 RTK Query 는 캐싱 관련 기능도 지원하기 때문에, 불필요한 요청을 줄일 수도 있다. 그리고 post 하자마자 자동으로 get 하는 기능까지도 구현할 수 있다.

뿐만 아니라, 프론트 데이터와 api 데이터 관리하는 로직 자체를 분리시킬 수도 있다.

백엔드와의 요청을 통해서 받아오는 데이터 → RTK Query 로 관리

프론트엔드만의 데이터 → createSlice 로 관리

뭔 소리인지 모르겠다. 일단 해보자.

앱을 새로 만들어서 작업한다.

이전에 service key를 .env 에 명시했던 것처럼, 백엔드 주소를 .env에 명시해준다.

VITE_SERVER_URL='https://jsonplaceholder.typicode.com'

이렇게 작성하면 import.meta.env.VITE_SERVER_URL라고 작성해서 해당 주소값을 가져올수 있다.

2) createApi 화용

api는 createApi라는 함수를 사용해서 만들 수 있다.
해당 함수 안에, 특정한 주소와 관련한 요청을 보두 작성하는 방식이다.

api/postApi.js

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
 
export const api이름 = createApi({
  reducerPath: '리듀서이름',
  baseQuery: fetchBaseQuery({
    baseUrl: import.meta.env.VITE_SERVER_URL,
  }),
  endpoints: (builder) => ({
    요청이름: builder.query({
      query: (주소에 넘길 값) => 'api 주소값/(주소에 넘길 값)',
    }),
  }),
});
 
export const {
  use요청이름Query
} = postApi;

endpoints 에다가 보내고자 하는 요청 이름을 명시하면, 자동으로 use요청이름 Query를 사용할수 있게 된다.

요청을 명시할때 builder.query라고 작성하게 되면, get요청을 보낼수 있게 되고
builder.mutation이라고 작성하면, post혹은 put 요청 등을 보낼수 있게 된다.

예를 들어서, endpoints 쪽에 아래처럼 작성하면 https://jsonplaceholder.typicode.com/posts 로 get요청을 보내는 동작이 만들어 지는 것이다.

getPosts: builder.query({
      query: () => 'posts',
    }),

그 후에 컴포넌트에서 아래처럼 사용하면, 동작을 수행하면서 자동으로 isLoading, isError, data를 반환해준다. 우리가 직접 isError 등을 정의해줄 필요없이 바로 받아와서 사용해줄수 있는 것이다.

const { data: posts, isLoading, isError } = useGetPostsQuery();

3)createApi 활용(posts 에 요청보내기)

post와 관련한 데이터를 받아올 수 있도록 ,아래처럼 작성한다.

getPosts라는 endpoint를 만든다.

api/postApi.js

import { createApi,fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const postApi = createApi({
    reducderPath: 'postApi',
    baseQuery: fetchBaseQuery({
        baseUrl: import.meta.env.VITE_SERVER_URL,
    }),
    endpoints: (builder) => ({
        getPosts: builder.query({
            query: () => 'posts',
        }),
    }),
})

export const { useGetPostsQuery } = postApi

store/index.js에는 이렇게 작성한다.

import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { postApi } from '../api/postApi';
 
const store = configureStore({
  reducer: {
    [postApi.reducerPath]: postApi.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(postApi.middleware),
});
setupListeners(store.dispatch);

export default store

setupListeners는 서비스 내에서 사용자의 행동이나 요소의 변화를 지켜보다가 요청을 보낼수 있도록 listener를 준비해놓기 위한 함수이다. (refetchOnMount, refetchOnreconnect 기능을 위함)

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) => (
                <div key={post.id}>
                    <p>{post.title}</p>
                    <p>{post.body}</p>
                </div>
            ))}
        </div>
    )
}

export default Posts

이제 app.js 는 항상 하던대로 provider로 감싸준다

import Posts from './components/Posts'
import { Provider } from 'react-redux'
import store from './store/index'
function App() {
    return (
        <Provider store={store}>
            <Posts />
        </Provider>
    )
}

export default App

4) 옵션 활용해보기 (cache, refetch)

  • cache 시간 설정하기

RTK QUERY는 자동으로 데이터를 캐싱한다. 이미 한번 요청을 보냈다면, 일정시간 동안은 해당 응답 데이터를 재사용한다. (기본 60초)

실제로 그런지 확인해보자.

app.jsx

import React, { useState } from 'react'
import { Provider } from 'react-redux'
import Posts from './components/Posts'
import store from './store/index'

function App() {
    const [isClicked, setIsClicked] = useState()

    return (
        <Provider store={store}>
            <button type="button" onClick={() => setIsClicked(!isClicked)}>
                보이게하기
            </button>
            {isClicked ? <Posts /> : null}
        </Provider>
    )
}

export default App

이렇게 바꾸고, 버튼을 누를 때마다 Posts 컴포넌트의 렌더링 여부가 달라지도록 해준다.

한번 시간을 변경해보자.

postApi 에 KeepUnusedDatafor(컴포넌트가 렌더링 되지 않더라도 몇초동안 데이터를 보관할지)를 1초로 설정한다.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const postApi = createApi({
    reducderPath: 'postApi',
    baseQuery: fetchBaseQuery({
        baseUrl: import.meta.env.VITE_SERVER_URL,
    }),
    endpoints: (builder) => ({
        getPosts: builder.query({
          query: () => 'posts',
          keepUnusedDataFor: 1 // 유지 시간
          
        }),
    }),
})

export const { useGetPostsQuery } = postApi

이렇게 해주면, 사용자가 컴포넌트가 1초 이상 보이지 않았다면, 컴포넌트를 보이게 할때마다 다시 요청을 보내는 것을 확인할수 있다.

  • 화면에 Focus 하면 자동으로 refetch하기 (refetchOnFocus)
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
 
export const postApi = createApi({
  reducerPath: 'postApi',
  baseQuery: fetchBaseQuery({
    baseUrl: import.meta.env.VITE_SERVER_URL,
  }),
  refetchOnFocus: true, // 추가
  endpoints: (builder) => ({
    getPosts: builder.query({
      query: () => 'posts',
    }),
  }),
});
 
export const {
  useGetPostsQuery
} = postApi;

이렇게 해주면 화면에 Focus 할때마다 자동으로 요청을 보낸다.

만약 글 하나만 보고싶으면 어떻게 하지?
postApi 코드의 endpoints 에 getPost를 추가

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const postApi = createApi({
    reducerPath: 'postApi',
    baseQuery: fetchBaseQuery({
        baseUrl: import.meta.env.VITE_SERVER_URL,
    }),
    refetchOnFocus: true, // 추가
    endpoints: (builder) => ({
        getPosts: builder.query({
            query: () => 'posts',
        }),
        getPost: builder.query({
            query: (postId) => `posts/${postId}`,
        }),
    }),
})

export const { useGetPostsQuery, useGetPostQuery } = postApi

컴포넌트에서 useGetPostsQuery 대신 useGetPostQuery를 사용해야한다!
또한 id값을 숫자로 적어넣어야한다.

import React from 'react'
import { useGetPostQuery, useGetPostsQuery } from '../api/postApi'

function Posts() {
    const { data: post, isLoading, isError } = useGetPostQuery(3)

    if (isLoading) {
        return <div>로딩 중...</div>
    }

    return (
        <div>
            {/*   {posts.map((post) => (
                <div key={post.id}>
                    <p>{post.title}</p>
                    <p>{post.body}</p>
                </div>
            ))} */}
            <div key={post.id}>
                <p>{post.title}</p>
                <p>{post.body}</p>
            </div>
        </div>
    )
}

export default Posts

5) createApi 활용 기능 추가

쿼리에 매개변수를 받아서 요청을 보내는 방법과, post 요청을 보내는 방법을 살펴본다.

매개변수는 아래처럼 api에 작성하고,

query: (매개변수) => `매개변수를 활용한 주소 작성`

컴포넌트에서 쿼리 함수 실행 시에 매개변수를 명시해주면 된다.

쿼리함수(매개변수)

get 요청 이외의 요청의 경우, mutation이라는 함수를 사용해야한다.

createPost: builder.mutation({ // mutation 이라고 작성
  query: ({ data }) => ({ // 요청 시 받을 데이터
    url: 'posts',
    method: 'POST', // method 방식을 명시
    body: data, // body 에 데이터를 명시
  }),
}),

api/PostApi

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const postApi = createApi({
    reducerPath: 'postApi',
    baseQuery: fetchBaseQuery({
        baseUrl: import.meta.env.VITE_SERVER_URL,
    }),
    endpoints: (builder) => ({
        getPosts: builder.query({
            query: () => 'posts',
        }),
        getPostById: builder.query({
            query: (postId) => `posts/${postId}`, // () 에 매개변수를 명시
        }),
        createPost: builder.mutation({
            // mutation 이라고 작성
            query: ({ data }) => ({
                // 요청 시 받을 데이터
                url: 'posts',
                method: 'POST', // method 방식을 명시
                body: data, // body 에 데이터를 명시
            }),
        }),
    }),
})

export const { useGetPostsQuery, useGetPostByIdQuery, useCreatePostMutation } = postApi

input을 받아서 post 요청을 보내는 컴포넌트 PostInput를 제작한다.

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;

createPost 부분에 data를 명시할수 있는 것은 postApi.js에 data를 받아서 요청 보내도록 작성해놨기 때문이다.

app.js에다가 아래처럼 컴포넌트를 추가한다.

import React, { useState } from 'react'
import { Provider } from 'react-redux'
import Posts from './components/Posts'
import store from './store/index'
import Post from './components/Post'
import PostInput from './components/PostInput'
function App() {
    const [isClicked, setIsClicked] = useState()

    return (
        <Provider store={store}>
            <PostInput />
            <button type="button" onClick={() => setIsClicked(!isClicked)}>
                보이게하기
            </button>
            {isClicked ? <Posts /> : null}
            <Post />
        </Provider>
    )
}

export default App

input 창에 내용을 입력하고 save 버튼을 눌렀을때 상태값이 201이 나온다면 성공이다.

6) Tag 활용(post후 자동 get 요청 보내기)

이제 Tag 라는 것을 각 요청 별로 설정해서, post 후 자동 get 요청 보내기를 진행한다.

여기서 Tag는 각 요청별 cache 의 이름을 뜻한다고 생각하면 된다.

이 Tag를 invalidate 한다는 것은, 기존에 요청 보냈던 결과 cache를 없애고, 해당 Tag에 대한 요청을 다시 보내는 것을 말한다.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const postApi = createApi({
    reducerPath: 'postApi',
    baseQuery: fetchBaseQuery({
        baseUrl: import.meta.env.VITE_SERVER_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({
            // mutation 이라고 작성
            query: ({ data }) => ({
                // 요청 시 받을 데이터
                url: 'posts',
                method: 'POST', // method 방식을 명시
                body: data, // body 에 데이터를 명시
            }),
            invalidatesTags: (result) => (result ? [{ type: 'Posts', id: 'LIST' }] : []),
        }),
    }),
})

export const { useGetPostsQuery, useGetPostByIdQuery, useCreatePostMutation } = postApi

-> createPost 요청을 보내거, 거기에 대해 result가 있을때만 {type: 'Posts',id:'LIST'} 라는 태그를 가진 getPosts 요청을 다시 보내는 것이다.

버튼으로 토글해서 보여지게 햇던 Posts를 아래와 같이 수정하고

import React, { useState } from 'react'
import { Provider } from 'react-redux'
import Posts from './components/Posts'
import store from './store/index'
import Post from './components/Post'
import PostInput from './components/PostInput'
function App() {
    const [isClicked, setIsClicked] = useState()

    return (
        <Provider store={store}>
            <PostInput />
            <Posts />
            <Post />
        </Provider>
    )
}

export default App

실행해서 input을 입력하고 save를 누르면?

post 요청을 하고 나서 바로 get 요청을 하는 모습을 볼수 있다.

profile
개발자 꿈나무

0개의 댓글