Front-End/React

[React] API ๋ฐ์ดํ„ฐ ์บ์‹ฑ/ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ์ž๋™ ๊ฐฑ์‹ ์„ ์œ„ํ•œ Reudx Toolkit Query

Splin 2023. 2. 21.

ํšŒ์‚ฌ ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ์ค‘ ์ „์—ญ์ ์œผ๋กœ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ(์›Œํฌ์ŠคํŽ˜์ด์Šค, ์ฑ„๋„, ์œ ์ € ์ •๋ณด ๋“ฑ๋“ฑ...)๋ฅผ ๋‹ค๋ฃฐ ๋•Œ, ํ•ด๋‹น ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•  ํ•„์š”์„ฑ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

React-query์™€ Redux ToolKit Query ์ค‘ ์–ด๋А ๊ฒƒ์„ ์ ์šฉํ• ์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ํ–ˆ๋Š”๋ฐ, ๊ธฐ์กด ํ”„๋กœ์ ํŠธ ํ”„๋ก ํŠธ์—์„œ ์ด๋ฏธ Redux ToolKit๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ  ๊ธฐ๋Šฅ๋„ React-query์™€ ๋น„์Šทํ•ด์„œ Redux ToolKit Query๋ฅผ ์ ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉํ•˜๋‹ค๋ณด๋‹ˆ, axios ๋Œ€์‹  ์‚ฌ์šฉํ•ด๋„ ๊ดœ์ฐฎ์„ ๊ฑฐ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“œ๋„ค์š” ๐Ÿ˜€

 

ํ•ด๋‹น ๊ธ€์€ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ Redux ToolKit Query ์ ์šฉ ๋ฐ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์— ๋Œ€ํ•˜์—ฌ ๊ธฐ์žฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

์ง€์†์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ ์˜ˆ์ •์ด๋ฉฐ, ์–ธ๊ธ‰๋˜์ง€ ์•Š์€ ์˜ต์…˜๋„ ๋งŽ์œผ๋‹ˆ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค๋ฉด ์•„๋ž˜์˜ Redux Toolkit ๊ณต์‹๋ฌธ์„œ๋ฅผ ์‚ดํŽด๋ณด๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค

 

 

0. RTK(Redux ToolKit) Query๋ž€?

Redux ToolKit(์ดํ•˜ RTK) Query๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๋ฐ ์บ์‹ฑ์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

๋˜ํ•œ, ๊ธฐ์กด์— Redux๋กœ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜์˜€์ง€๋งŒ, ๋ช‡ ๋…„ ๋™์•ˆ React ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ํ†ตํ•ด ์ƒํƒœ๊ด€๋ฆฌ์™€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๋ฐ ์บ์‹ฑ์„ ๋‹ค๋ฅธ ๊ด€์‹ฌ์‚ฌ๋กœ ๋ณด๊ณ  ์ œ์ž‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

RTK Query๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜ ๋™์ž‘๋“ค์„ ์†์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • UI ์Šคํ”ผ๋„ˆ๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•œ ๋กœ๋“œ ์ƒํƒœ ์ถ”์ 
  • ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€
  • UI๊ฐ€ ๋” ๋น ๋ฅด๊ฒŒ ๋А๊ปด์ง€๋„๋ก ์—…๋ฐ์ดํŠธ
  • ์‚ฌ์šฉ์ž๊ฐ€ UI์™€ ์ƒํ˜ธ ์ž‘์šฉํ•  ๋•Œ์˜ ์บ์‹œ ์ˆ˜๋ช… ๊ด€๋ฆฌ

 

 

1. RKT Query ๊ตฌ์กฐ ๋ฐ Store ๋“ฑ๋ก ๋ฐฉ๋ฒ•

RTK Query๋Š” ์ด๋ฆ„๊ณผ ๊ฐ™์ด Redux Toolkit์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์—, ๊ธฐ๋ณธ RTK์˜ store์— ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. (RTK Query์— ๊ด€ํ•œ ๊ธ€์ด๊ธฐ ๋•Œ๋ฌธ์— RTK์— ๊ด€ํ•œ ์„ค๋ช…์€ ์ œ์™ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.)

RTK์ฒ˜๋Ÿผ createSlice()๋กœ ์Šฌ๋ผ์ด์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์•„๋‹Œ, createApi ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๋งŒ๋“ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

export const apiSlice = createApi({
  reducerPath: 'api', // store์— ๋“ฑ๋ก๋  Api ๊ณ ์œ  ํ‚ค
  baseQuery: refreshFetchBase, // endpoints์— ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉ๋  baseQuery. baseQuery๋ฅผ ์ด์šฉํ•ด axios์˜ ํ—ค๋” ์„ค์ •, intercept ๊ธฐ๋Šฅ์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Œ
  endpoints: (builder) => ({ // api๋ฅผ ๊ธฐ์žฌํ•˜๋Š” ์˜์—ญ
  tagTypes: ['Item'], // ๋ฌธ์ž์—ด ํƒœ๊ทธ ๋ฐฐ์—ด. ํ•ด๋‹น ํƒœ๊ทธ๋กœ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ๊ฐ€๋Šฅ
    ...
  }),
});

 

 

1-1. endpotins ๋ถ„ํ• 

๊ธฐ๋ณธ ์ ์œผ๋กœ RTK Query๋Š” ํ•œ ๊ณณ์—์„œ Api๋ฅผ ์ •์˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์„œ๋น„์Šค ํฌ๊ธฐ๊ฐ€ ์ปค์ง€๋ฉด API๊ฐ€ ๋งŽ์•„์ ธ ํŒŒ์ผ์ด ์ ์  ์ปค์ง€๋ฉด์„œ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์›Œ ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋•Œ๋ฌธ์— injectEndpoints๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ด€๋œ ๋ฒ”์œ„ ๋ณ„(๋ณดํ†ต tag ๋ณ„)๋กœ endpoints๋ฅผ ๋‚˜๋ˆ„๋Š” ๊ฒŒ ์ข‹์Šต๋‹ˆ๋‹ค. (์•„์‰ฝ๊ฒŒ๋„ createApi ์— ์ •์˜๋œ ๊ธฐ๋ณธ ์„ค์ •์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.)

 

injectEndpoints

๊ธฐ์กด api ์Šฌ๋ผ์ด์Šค์— ์กด์žฌํ•˜๋Š” endpoints์™€ ๊ฐ™์€ ์ด๋ฆ„์œผ๋กœ endpoints๋ฅผ ์ •์˜ํ•œ๋‹ค๋ฉด overrideํ•ฉ๋‹ˆ๋‹ค. (๋‹ค๋งŒ, overrideExisting์„ true๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด, ์žฌ์ •์˜ ๋˜์ง€ ์•Š๊ณ  ๊ฒฝ๊ณ  ํ‘œ์‹œ๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ false)

export const itemEndpoints = apiSlice.injectEndpoints({
  endpoints: (builder) => ({
	...
  }),
  // overrideExisting: true, ์ •์˜ํ•˜์ง€ ์•Š์œผ๋ฉด false
});

 

 

1-2. store์— ๋“ฑ๋ก

์œ„์— ์ฃผ์„์— ๊ธฐ์žฌ๋œ ๊ฒƒ ์ฒ˜๋Ÿผ Api ๊ณ ์œ  ํ‚ค๋ฅผ ์ด์šฉํ•˜์—ฌ store์— api๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export default configureStore({
  reducer: {
	...
 
    [apiSlice.reducerPath]: apiSlice.reducer, // reducerPath(ํ‚ค) : reducer(๊ฐ’) ๋ฐฉ์‹์œผ๋กœ ๋“ฑ๋ก
 
	...
  },
  middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }).concat(apiSlice.middleware), // middleware๋กœ ์—ฐ๊ฒฐํ•ด ์คŒ
});

 

 

2. endpoints ์ •์˜ ๋ฐฉ๋ฒ• ๋ฐ ์‚ฌ์šฉ๋ฒ•

์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” endpoints ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ ์ฃผ์„์„ ์ž‘์„ฑํ•˜์˜€์œผ๋ฉฐ, ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜์—์„œ ํ™•์ธํ•˜๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค. (์ฃผ์„์œผ๋กœ ์–ด๋А ๋ถ€๋ถ„์„ ๋ด์•ผํ•˜๋Š”์ง€ ํ‘œ์‹œํ•˜์˜€์Šต๋‹ˆ๋‹ค.

export const transformResponse = (baseQueryReturnValue, meta, arg) => baseQueryReturnValue.body;
 
// ์—๋Ÿฌ notify ๋…ธ์ถœ
const showErrorNotify = (response, message) => {
  notify.error(message);

  return response;
};
 
export const itemEndpoints = apiSlice.injectEndpoints({
  endpoints: (build) => ({
 
	...
 
    // ์•„์ดํ…œ ์ •๋ณด ์กฐํšŒ
    fetchItem: build.query({
      query: (itemNo) => `/items/${itemNo}`, // api ์ •์˜(2-1. build์˜ query, mutation ์ฐธ๊ณ )
      providesTags: ['Item'], // invalidatesTags์— ๊ฐ™์€ ํƒœ๊ทธ๊ฐ€ ๋ถ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ ์‹œ ํ•ด๋‹น api๊ฐ€ ๊ฐฑ์‹ (2-2. tag์‹œ์Šคํ…œ ์ฐธ๊ณ )
      transformResponse, // response๋ฅผ ๋ณ€ํ˜•์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ(2-3. custom response ๊ณ )
      transformErrorResponse: (response) => showErrorNotify(response, '์•„์ดํ…œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์–ด์š”'), // error response๋ฅผ ๋ณ€ํ˜•์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ(2-3. custom response ์ฐธ๊ณ )
    }),
    // ์•„์ดํ…œ ์ •๋ณด ๋ณ€๊ฒฝ
    updateItem: build.mutation({
      query: ({ itemNo, ...data} ) => ({
        url: `/items/${itemNo}`,
        method: 'PUT',
        body: data,
      }),
      invalidatesTags: ['Item'], // ํ•ด๋‹น ํƒœ๊ทธ๊ฐ€ ๋ถ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ ์‹œ providesTags์— ๊ฐ™์€ ํƒœ๊ทธ๊ฐ€ ๋ถ™์€ api๋ฅผ ๊ฐฑ์‹ (2-2. tag์‹œ์Šคํ…œ ์ฐธ๊ณ )
    }),
    // ์•„์ดํ…œ ์ˆ˜ ์กฐํšŒ
    fetchItemCount: build.query({
      query: (itemNo) => `/items/${itemNo}/members/count`,
      providesTags: [{ type: 'Item', id: tags.membersCount }],
      transformResponse,
      transformErrorResponse: (response) => showErrorNotify(response, '์•„์ดํ…œ ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์–ด์š”'),
    }),

	...
 
  }),
});
 
// react์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” hook ํ˜•์‹์œผ๋กœ export, RTK Query๊ฐ€ hook์„ ์ œ๊ณต
// use + ๋ฉ”์„œ๋“œ ์ด๋ฆ„ + Query/Mutation ์œผ๋กœ ์ด๋ฃจ์–ด์ง
// 2-1. build์˜ query, mutation ์ฐธ๊ณ 
export const {
  useFetchItemQuery, 
  useUpdateItemMutation,
  useLazyFetchItemCountQuery, // Lazy ์ฆ‰ ์ˆ˜๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณต
  ...
}

 

 

2-1. build์˜ query(๋ฐ์ดํ„ฐ ์กฐํšŒ), mutation(๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ)

๋ฐ์ดํ„ฐ ์กฐํšŒ ๋ฐ ๋ณ€๊ฒฝ ์‹œ endpoints์˜ query(์กฐํšŒ)์™€ mutation(๋ณ€๊ฒฝ) ๋ฉ”์„œ๋“œ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น api๋ฅผ ์š”์ฒญํ•  ๋•Œ, RTK Query๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” hook์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, api์— ์—ฌ๋Ÿฌ๊ฐœ์˜ parameter๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ฐ˜๋“œ์‹œ ๊ฐ์ฒด๋กœ ์ œ๊ณตํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

 

query

๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ ์‚ฌ์šฉํ•˜๋Š” endpoints build ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. javascript fetch ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•œ fetchBaseQuery๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

ํ•ด๋‹น build ๋ฉ”์„œ๋“œ๋กœ endpoints๋ฅผ ์ •์˜ํ•˜๊ณ  RTK Query๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” ์กฐํšŒ hook์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น API ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

RTK Query๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” query ๊ด€๋ จ hook์€ ์•„๋ž˜์™€ ๊ฐ™์ด 5๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ๋Š” useQuery, useLazyQuery๋งŒ ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค. 

  • useQuery : api ๋ฐ์ดํ„ฐ ์š”์ฒญ or ์บ์‹œ ๋ฐ์ดํ„ฐ ์กฐํšŒ
  • useLazyQuery : api ๋ฐ์ดํ„ฐ ์กฐํšŒ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ œ์–ด
  • useQueryState : ์š”์ฒญ ์ƒํƒœ ๋ฐ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒ
  • useQuerySubscription : ์žฌ์กฐํšŒ ๋ฐ ์บ์‹œ ๋ฐ์ดํ„ฐ ๊ตฌ๋…
  • useLazyQuerySubscription : ์žฌ์กฐํšŒ ๋ฐ ์บ์‹œ ๋ฐ์ดํ„ฐ ๊ตฌ๋…์„ ์ˆ˜๋™์œผ๋กœ ์ œ์–ด

 

useQuery

useQuery๋Š” use + endpoints์ด๋ฆ„ + Query ํ˜•์‹์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. hook์˜ parameter์— ๋“ค์–ด๊ฐˆ ๊ฐ’๊ณผ return๊ฐ’์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • useQuery hook์˜ parameter : api์— ์‚ฌ์šฉ๋  parameter์™€ queryOptions์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • useQuery hook์˜ return : ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ isLoading, isSuccess๊ฐ™์€ ์š”์ฒญ์ƒํƒœ ๊ฐ’๊ณผ data,error, ์‹œ์ž‘/์ข…๋ฃŒ์‹œ๊ฐ„ ๋“ฑ data๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

// itemEndpoints.js
export const itemEndpoints = apiSlice.injectEndpoints({
  endpoints: (build) => ({
    // ์•„์ดํ…œ ์ •๋ณด ์กฐํšŒ
    fetchItem: build.query({ // query ๋ฉ”์„œ๋“œ๋กœ api ์ •์˜
      query: (itemNo) => `/items/${itemNo}`,
      providesTags: ['Item'],
      transformResponse,
      transformErrorResponse: (response) => showErrorNotify(response, '์•„์ดํ…œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์–ด์š”'),
    }),
  }),
});
 
export const {
  useFetchItemQuery, // RTK Query hook ์ถ”์ถœ
}



// ItemInfo.jsx
const ItemInfo = ({ itemNo }) => {

  ...
 
  // ์ž๋™์œผ๋กœ api๋ฅผ ์š”์ฒญ, 
  // api.endpoints.fetchItem.useQuery(itemNo) ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  const { data: item = [], isSuccess } = useFetchItemQuery(itemNo); // hook์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ api์— ์‚ฌ์šฉ๋  ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ „๋‹ฌ
}

๊ธฐ์กด์— axios๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์š”์ฒญ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์„ state๊ฐ€ ํ•„์š”ํ–ˆ๋Š”๋ฐ, RTK Query์˜ hook์„ ์‚ฌ์šฉํ•˜๋ฉด ์œ„์™€ ๊ฐ™์ด hook์„ ์ƒ์„ฑํ•œ ํ›„ data๋ฅผ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. (์ถ”๊ฐ€๋กœ isLoading ๊ฐ™์€ ์ƒํƒœ๊ฐ’๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ)

RTK Query์˜ parameter, return ๊ฐ’์€ ์•„๋ž˜ ๋งํฌ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

useLazyQuery

useLazyQuery๋Š” ์ˆ˜๋™์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์‹คํ–‰ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

 hook์˜ ํ˜•์‹์€ useLazy + endpoints์ด๋ฆ„ + Query ํ˜•์‹์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

hook์˜ parameter์— ๋“ค์–ด๊ฐˆ ๊ฐ’๊ณผ return๊ฐ’์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • useLazyQuery hook์˜ parameter : api ์‹คํ–‰ ์‹œ์˜ queryOptions์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • useLazyQuery hook์˜ return : ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, [ ์‹คํ–‰ ๋ฉ”์„œ๋“œ, ๊ฒฐ๊ณผ ๊ฐ์ฒด, lastPromiseInfo ] ๋กœ ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

// itemEndpoints.js
export const itemEndpoints = apiSlice.injectEndpoints({
  endpoints: (build) => ({
	// ์•„์ดํ…œ ์ˆ˜ ์กฐํšŒ
	fetchItemCount: build.query({ // query ๋ฉ”์„œ๋“œ๋กœ api ์ •์˜
	  query: (itemNo) => `/items/${itemNo}/count`,
	  providesTags: [{ type: 'Item', id: tags.count }],
	  transformResponse,
	  transformErrorResponse: (response) => showErrorNotify(response, '์•„์ดํ…œ ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์–ด์š”'),
	}),
  }),
});
 
export const {
  useLazyFetchItemCountQuery, // RTK LazyQuery hook ์ถ”์ถœ
}


// ItemAuthorityManage.jsx
const ItemAuthorityManage = ({ itemNo, setSelectedMember }) => {

  ...
 
  // ํ•ด๋‹น ์‹คํ–‰ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•จ์œผ๋กœ์จ api๋ฅผ ์š”์ฒญ (์ž๋™์œผ๋กœ api๋ฅผ ์š”์ฒญ X)
  // api.endpoints.fetchItemCount.useLazyQuery() ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  const [fetchItemCount] = useLazyFetchItemCountQuery();
  const [fetchInvited] = useLazyFetchInvitedQuery();
 
  const moveMemberInvite = useCallback(async () => {
    const [itemCount, invitedMembers] = await axios.all([
      fetchItemMembersCount(itemNo).unwrap(), // unwrap()์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฒฐ๊ณผ ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋จ
      fetchInvited(itemNo).unwrap(),
    ]);
 
	...
 
  }
}

useLazyQuery๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ž๋™์œผ๋กœ api๋ฅผ ์š”์ฒญํ–ˆ๋Š”๋ฐ, useLazyQuery๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์‹คํ–‰๋ฉ”์„œ๋“œ ์‹คํ–‰ ์‹œ์ ์— api๋ฅผ ์š”์ฒญํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด ๋•Œ, unwrap() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ์˜ response๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š๊ณ  isFetching, data ๋“ฑ๋“ฑ์ด ๋“ค์–ด์žˆ๋Š” useLazyQuery์˜ ๊ฒฐ๊ณผ ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

RTK LazyQuery์˜ parameter, return ๊ฐ’์€ ์•„๋ž˜ ๋งํฌ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

mutation

๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์‚ฌ์šฉํ•˜๋Š” endpoints build ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

ํ•ด๋‹น build ๋ฉ”์„œ๋“œ๋กœ endpoints๋ฅผ ์ •์˜ํ•˜๊ณ  RTK Query๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋ณ€๊ฒฝ hook์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น API ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

RTK Query๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋ณ€๊ฒฝ hook์€ useMutation ํ•˜๋‚˜ ๋ฟ์ž…๋‹ˆ๋‹ค.

 

useMutation

useMutation๋Š” use + endpoints์ด๋ฆ„ + Mutation ํ˜•์‹์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.hook์˜ parameter์— ๋“ค์–ด๊ฐˆ ๊ฐ’๊ณผ return๊ฐ’์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • useMutation hook์˜ parameter : api ์‹คํ–‰ ์‹œ์˜ queryOptions์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • useMutation hook์˜ return ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, [ ์‹คํ–‰ ๋ฉ”์„œ๋“œ, ๊ฒฐ๊ณผ ๊ฐ์ฒด ] ๋กœ ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

// itemEndpoints.js
export const itemEndpoints = apiSlice.injectEndpoints({
  endpoints: (build) => ({
	// ์•„์ดํ…œ ์ •๋ณด ๋ณ€๊ฒฝ
	updateItem: build.mutation({ // mutation ๋ฉ”์„œ๋“œ๋กœ api ์ •์˜
	  query: ({ itemNo, ...data }) => ({ // ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ณต
	    url: `/items/${itemNo}`,
	    method: 'PUT',
	    body: data,
	  }),
	  invalidatesTags: ['Item'],
	}),
  }),
});
 
export const {
  useUpdateItemMutation, // RTK Mutation hook ์ถ”์ถœ
}


// ItemInfo.jsx
const ItemInfo = ({ itemNo }) => {
 
  // ํ•ด๋‹น ์‹คํ–‰ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•จ์œผ๋กœ์จ api๋ฅผ ์š”์ฒญ
  // api.endpoints.updateItem.useMutation() ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  const [updateItem] = useUpdateItemMutation();

  ...
 
  const onUpdateItem = async (data) => {
	const params = {
	  itemNo: data.no,
	  itemName: data.name,
	};
	try {
	  await updateItem(params).unwrap(); // unwrap()์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฒฐ๊ณผ ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋จ
	  notify.info('์•„์ดํ…œ ์ •๋ณด๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
	  closeModal('profileSettings');
	} catch (error) {
	  notify.error('์•„์ดํ…œ ์ •๋ณด ์ˆ˜์ •์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
	}
  }
}

useMutation์˜ ์‹คํ–‰๋ฉ”์„œ๋“œ ์‹คํ–‰ ์‹œ์ ์— api๋ฅผ ์š”์ฒญํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด ๋•Œ, unwrap() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ์˜ response๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š๊ณ  isFetching, data ๋“ฑ๋“ฑ์ด ๋“ค์–ด์žˆ๋Š” useMutation์˜ ๊ฒฐ๊ณผ ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

RTK MutationQuery์˜ parameter, return ๊ฐ’์€ ์•„๋ž˜ ๋งํฌ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

2-2. tag ์‹œ์Šคํ…œ(์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ์ž๋™ ๊ฐฑ์‹ )

๊ธฐ๋ณธ์ ์œผ๋กœ RTK Query๋Š” ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋งŒ API ์š”์ฒญ์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Mutation์„ ํ†ตํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ, ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์š”์ฒญํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ์— tag ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (RTK Query์—์„œ๋Š” ๊ฐ endpoint + ๋งค๊ฐœ๋ณ€์ˆ˜ ์กฐํ•ฉ์œผ๋กœ CacheKey๋ฅผ ๋งŒ๋“ค์–ด, ํŠน์ • ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

// tag์˜ type
export const tagTypes = {
  item: 'Item',
  paperTemplate: 'PaperTemplate',
};
 
export const apiSlice = createApi({
  reducerPath: 'api',
  baseQuery: refreshFetchBase,
  tagTypes: [Object.values(tagTypes)], // ['Item', 'PaperTemplate']๊ณผ ๊ฐ™์Œ. ๋ฌธ์ž์—ด ํƒœ๊ทธ ๋ฐฐ์—ด. ํ•ด๋‹น ํƒœ๊ทธ๋กœ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ๊ฐ€๋Šฅ
  endpoints: (builder) => ({ 
	// ์•„์ดํ…œ ์ •๋ณด ์กฐํšŒ
	fetchItem: build.query({
	  query: (itemNo) => `/items/${itemNo}`,
	  providesTags: [item], // invalidatesTags์— ๊ฐ™์€ ํƒœ๊ทธ๊ฐ€ ๋ถ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ ์‹œ ํ•ด๋‹น api๊ฐ€ ๊ฐฑ์‹ 
	  transformResponse,
	  transformErrorResponse: (response) => showErrorNotify(response, '์•„์ดํ…œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์–ด์š”'),
	}),
	// ์•„์ดํ…œ ์ •๋ณด ๋ณ€๊ฒฝ
	updateItem: build.mutation({
	  query: ({ itemNo, ...data }) => ({
	    url: `/items/${itemNo}`,
	    method: 'PUT',
	    body: data,
	  }),
	  invalidatesTags: [item], // ํ•ด๋‹น ํƒœ๊ทธ๊ฐ€ ๋ถ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ ์‹œ providesTags์— ๊ฐ™์€ ํƒœ๊ทธ๊ฐ€ ๋ถ™์€ api๋ฅผ ๊ฐฑ์‹ 
	}),
	// ์•„์ดํ…œ ์ˆ˜ ์กฐํšŒ
	fetchItemCount: build.query({
	  query: (itemNo) => `/items/${itemNo}/count`,
	  providesTags: [{ type: item, id: tagIds.count }], // id๋ฅผ ์ง€์ •ํ•จ์œผ๋กœ์จ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋””ํ…Œ์ผํ•˜๊ฒŒ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ
	  transformResponse,
	  transformErrorResponse: (response) => showErrorNotify(response, '์•„์ดํ…œ ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์–ด์š”'),
	}),
  }),
});

 

 

tagTypes

Slice์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํƒœ๊ทธ๋“ค์€ tagTypes์— ๋ฌธ์ž์—ด ๋ฐฐ์—ด๋กœ ๊ธฐ์žฌํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. 

 

 

providesTags

์ด๋ฆ„๊ณผ ๊ฐ™์ด ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ์— ํƒœ๊ทธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํƒœ๊ทธ์˜ ํ‘œํ˜„ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1.  ['Item'] : tagTypes์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์ œ๊ณต
    • ๋ฐฐ์—ด๋กœ ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ์œ ์ถ”ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํƒœ๊ทธ ์ง€์ • ๊ฐ€๋Šฅ
    • ์ฆ‰, ์œ„์˜ fetchWorkspace endpoint์— ['Workspace', 'EvaluationTemplate'] ๋กœ ์ง€์ •ํ•ด์ค€๋‹ค๋ฉด EvaluationTemplate ์™€ ์—ฐ๊ด€๋˜์–ด ๊ฐฑ์‹ ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์˜๋ฏธ
  2. [{type: 'Item'}] : ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ์ œ๊ณต
    • ํ˜•ํƒœ๋งŒ ๋‹ค๋ฅผ ๋ฟ 1๋ฒˆ๊ณผ ๊ฐ™์Œ
  3. [{type: 'Item', id: 1}] : type๊ณผ id ํ˜•ํƒœ๋กœ ์ œ๊ณต
    • id๋ฅผ ์ด์šฉํ•˜์—ฌ ์กฐ๊ธˆ ๋” ๋””ํ…Œ์ผํ•˜๊ฒŒ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ

 

id๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

providesTags์— ํƒœ๊ทธ๋ฅผ ์ง€์ •ํ•  ๋•Œ, ๋ฐฐ์—ด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ (result, error, arg) => {} ๊ฐ™์€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ค„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์•„๋ž˜์˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ id๋กœ ๋””ํ…Œ์ผํ•˜๊ฒŒ ์บ์‹œ ๋ฐ์ดํ„ฐ ๊ตฌ๋ถ„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

providesTags: (result, error, arg) => result ? [ ...result.map(({id}) => ({ type: 'Item', id })) ] : [{type: 'Item', id: 'LIST' }];

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ์ ์€ [{type: 'Workspace', id: 'LIST' }] ์ฒ˜๋Ÿผ id์— ๋ฌธ์ž์—ด ๊ฐ’์„ ๋„ฃ์–ด ๋””ํ…Œ์ผํ•œ ๋ถ€๋ถ„์„ ์ž„์˜๋กœ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

 

invalidatesTags

ํ•ด๋‹น ์†์„ฑ์„ ์ •์˜ํ•œ Mutation์„ ์‹คํ–‰ํ•˜๋ฉด providesTags์— ์ •์˜๋œ ํƒœ๊ทธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํŠน์ • ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

providesTags์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

 

 

ํƒœ๊ทธ ๊ฐฑ์‹  ๋™์ž‘ ๋ฒ”์œ„

type์— id๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ type๊ณผ ๊ด€๋ จ๋œ ์ „์ฒด ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•˜๊ฒŒ ๋˜๋‹ˆ ์ฃผ์˜ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Invalidated / Provided General tag A['Post']
/
[{ type: 'Post' }]
General tag B['User']
/
[{ type: 'User' }]
Specific tag A1[{ type: 'Post',
id: 1 }]
Specific tag A2[{ type: 'Post', id: 'LIST' }] Specific tag B1[{ type: 'User',
id: 1 }]
Specific tag B2[{ type: 'User',
id: 2 }]
General tag A
['Post'] / [{ type: 'Post' }]
โœ”๏ธ   โœ”๏ธ โœ”๏ธ    
General tag B
['User'] /
[{ type: 'User' }]
  โœ”๏ธ     โœ”๏ธ โœ”๏ธ
Specific tag A1
[{ type: 'Post', id: 1 }]
    โœ”๏ธ      
Specific tag A2
[{ type: 'Post', id: 'LIST' }]
      โœ”๏ธ    
Specific tag B1
[{ type: 'User', id: 1 }]
        โœ”๏ธ  
Specific tag B2
[{ type: 'User', id: 2 }]
          โœ”๏ธ

 

 

2-3. custom response(response ๊ตฌ์„ฑ ๋ณ€๊ฒฝ)

transformResponse์™€ transformErrorResponse๋ฅผ ์ด์šฉํ•˜๋ฉด endpoint์˜ ๊ฒฐ๊ณผ(์„ฑ๊ณต/์‹คํŒจ) ๊ฐ’์„ ๋ฐ›์„ ๋•Œ, ๊ฒฐ๊ณผ ๊ฐ’์„ Customํ•˜์—ฌ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

transformResponse

ํŠน์ • endpoint๊ฐ€ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€์ง€ ์•Š์œผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ํ•ด๋‹น ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด์„œ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

transformResponse: (response, meta, arg) => response.body;

 

transformErrorResponse

ํŠน์ • endpoint๊ฐ€ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ํ•ด๋‹น ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด์„œ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

transformErrorResponse: (response, meta, arg) => response.body;

 

๋Œ“๊ธ€