FrontEnd/React 심화

[React] Redux Toolkit 에서의 비동기 처리(createAsyncThunk, extraReducers)

기은P 2022. 9. 19. 18:01
반응형

1. 개요

앞서서 Redux Toolkit의 상태관리를 위한 기본적인 사용법에서 알아보았는데요, createSlice()를 통해 Slice라는 것을 생성하며, 여기에 초기 default 값과 dispatch를 통해 호출할 reducer 함수를 지정했었습니다.

프로그램 개발을 하다보면, 전역에서 자주 사용되는 api를 호출하거나, api 호출한 결과를 여러 군데에서 사용해야 할 상황이 생기는데, 이와 같은 비동기 처리를 redux store에서는 자체적으로 하지 못하는 단점이 있습니다.

따라서 Redux를 사용할 때는 redux-thunk, redux-saga와 같은 미들웨어를 사용해서 비동기 처리를 진행했습니다.

하지만 위의 기능들 같은 경우 Redux와 같이 사용하기 위한 러닝커브가 조금 있기 때문에, Redux Toolkit의 createAsyncThunk를 사용해 비동기 처리를 진행하도록 합니다.

 

 

2. createAsyncThunk()

createAsyncThunk()의 속성은 아래와 같습니다.

createAsyncThunk(typePrefix: string, payloadCreator: AsyncThunkPayloadCreator,
options?: AsyncThunkOptions): AsyncThunk

아래와 같이 사용합니다.

export const fetchUser = createAsyncThunk(
  'auth/profile',
  // 만약, api를 호출할때 object를 추가하고 싶으면 async (object, thunkAPI)로 호출합니다.
  async (_, thunkAPI) => {
    try {
      const response = await axios.get<{
        name: string
        email: string
        type: string
      }>('api/users/me')
      return response.data
    } catch (error) {
      return thunkAPI.rejectWithValue(error)
    }
  }
)

위와 같이 첫번째 파라미터로 typePrefix로 액션 타입을 생성하고, 두번째 파라미터로 promise 기반으로 결과를 반환하는 action을 작성합니다.

 

기억해야 할 점.

createAsyncThunk는 Promise의 3가지 상태와 같이 pending, fulfilled, rejected의 상태를 갖습니다.

저희가 기본적으로 비동기를 처리하는 promise를 사용하는 것처럼 함수를 작성해주시면 됩니다.

 

 

 

 

3. extraReducer

공식사이트 :: https://redux-toolkit.js.org/api/createReducer#usage-with-the-builder-callback-notation

위에서 비동기 처리를 할 액션을 정의했으니, 리듀서에 연결해야겠죠?

다만, 앞에서 설명했다시피 Redux에서는 자체적으로 비동기 처리를 지원하지 않아서, extraReducers라는 것을 사용해 createAsyncThunk로 생성한 Thunk를 등록시켜주어야합니다.

export const fetchSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUser.pending, (state) => {
      state.loading = 'pending';
    });
    builder.addCase(fetchUser.fulfilled, (state, action) => {
      state.loading = 'succeeded';
      state.user = action.payload;
    });
    builder.addCase(fetchUser.rejected, (state) => {
      state.loading = 'failed';
    });
  },
});

위와 같이 extraReducer에 파라미터인 builder를 통해 addCase를 사용해 case(pending 상태에 대한 처리, fulfilled에 대한 처리 등)를 등록해주어야 합니다.

promise 결과가 pending이면 대기중인 상태인 것이고,

fulfilled면 성공, rejected면 거절이니까, 그 상황에 맞게끔 반환되는 값을 state에 지정해주면 됩니다.

 

 

4. 사용

useDispatch를 통해 createAsyncThunk를 통해 만든 Thunk 함수를 호출하고, useSelecter를 통해 state값을 동기화 시켜줍니다.

먼저 전역에서 사용할 수 있는 dispatch hooks을 만들어줍니다.

 

store/index.ts

...
const combinedReducers = combineReducers({
  authReducer: authSlice.reducer,
  testReducer: testSlice.reducer,
  searchItemReducer: searchItemSlice.reducer,
})
export type OurStore = ReturnType<typeof combinedReducers>

const rootReducer = (
  state: ReturnType<typeof combinedReducers>,
  action: AnyAction
) => {
  if (action.type === HYDRATE) {
    const nextState = {
      ...state,
      ...action.payload,
    }
    return nextState
  }
  return combinedReducers(state, action)
}

export const store = configureStore<OurStore>({
  reducer: rootReducer,
})

// https://redux-toolkit.js.org/usage/usage-with-typescript#getting-the-dispatch-type
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch

...

 

searchForm.ts


import { useAppDispatch } from '../store'
import { unwrapResult } from '@reduxjs/toolkit';

const LoginPetch = () => {
  const dispatch = useAppDispatch();
  
  const { user, loading } = useSelector(state => state.postsState);
  

  useEffect(() => {
    const apiAction = dispatch(fetchUser());
    // unwrapResult 함수를 사용할 경우 바로 state의 값을 가져올 수 있다.
  	const apiResult = unwrapResult(apiAction);
  }, [dispatch]);
  
  return (
    <>
      <PostCardGrid user={user} />
      {loading !== 'idle' && <PostCardGridSkeleton />}
    </>
  );
};

 

반응형