bkdragon's log

[Redux-toolkit] 비동기 처리하기 본문

concept

[Redux-toolkit] 비동기 처리하기

bkdragon 2023. 9. 1. 23:53

Redux-toolkit 을 사용해 비동기 처리를 해보자. React-query를 사용해도 좋다.

 

Redux-toolkit에는 thunk 미들웨어가 내장되어 있어서 따로 설치할 필요가 없다.

 

Thunk

thunk는 특정 작업을 나중으로 미루기 위해 함수형태로 감싼것을 말한다. 

 

thunk는 Redux 스토어의 dispatch 및 getState 메서드를 사용하는 함수이다. 비동기 로직이 들어갈 수 있다.

const getItems = () => (dispatch, getState) => {
  // 현재 상태 조회
  const id = getState().item

  // 비동기 통신의 성공과 실패에 따라 다른 action을 dispatch
  api
    .getComments(id) 
    .then(comments => dispatch({ type: 'GET_COMMENTS_SUCCESS', id, comments })) 
    .catch(e => dispatch({ type: 'GET_COMMENTS_ERROR', error: e })); 
};

 

createAsyncThunk

createAsyncThunk를 사용하면 Redux-toolkit에서 Thunk를 사용할 수 있다.

 

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

 

  1. 액션 타입 문자열, 프로미스를 반환하는 비동기 함수 (payloadCreator) , 추가 옵션들을 인자로 받는다.
  2. 인자로 받은 문자열을 기반으로 프로미스 라이프사이클 액션 타입을 생성한다.
  3. 인자로 받은 프로미스 콜백을 실행하고 프로미스 라이프 사이클  thunk aciton creator(action을 생성하는) 를 반환한다.
  4. 생성된 action을 통해 store 변경하기 위한 reducer를 추가적으로 작성해야한다.
  5. fulfilled, rejected, pending 3가지 상태에 대해 reducer 를 작성할 수 있다.

 

공식 문서의 사용 예시 코드를 보자.

 

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId: number, thunkAPI) => {
    const response = await userAPI.fetchById(userId) // api 통신
    return response.data
  }
)

 

 

비동기 함수의 첫번째 인자로 api 통신을 하는데 유용한 userId를 받고 있다. 두번째 인자 thunkAPI는 Redux Thunk 함수에 전달되는 모든 매개변수와 추가 옵션이 포함된 객체이다. thunkAPI에 getState, dispatch 같은 것들이 들어있다.

비동기 함수의 return 값이 후에 생성할 reducer의 payload가 된다.

 

 

extraReducers

thunk는 extraReducer로 store에 연결할 수 있다.

 

const usersSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {
  },
  extraReducers: (builder) => {
    builder.addCase(fetchUserById.fulfilled, (state, action) => {
      state.entities.push(action.payload)
    });
     builder.addCase(fetchUserById.rejected, (state, action) => {
      // 실패 처리
    });
     builder.addCase(fetchUserById.pending, (state, action) => {
      // 대기 처리
    })
  },
})

 

성공, 실패, 대기에 대한 처리를 할 수 있다.

 

fetchUserById.fulfilled 와 같은 형태로 사용 가능한 이유는 createAsyncThunk의 반환값이 thunk action creator이기 때문이다. 

  • fetchUserById.pending :  'users/fetchByIdStatus/pending' 액션을 dispatch하는 액션 생성자.
  • fetchUserById.fulfilled :  'users/fetchByIdStatus/fulfilled' 액션을 dispatch하는 액션 생성자.
  • fetchUserById.rejected :  'users/fetchByIdStatus/rejected' 액션을 dispatch하는 액션 생성자.

기존의 action creator에서 비동기 통신 상태가 추가된 것으로 보면 이해가 편할 것 같다.

 

 

createAsyncThunk의 제네릭

제네릭을 이용해 thunkAPI의 타입을 지정 해줄 수 있다.

 

const fetchUserById = createAsyncThunk<
  // payload creator의 리턴 타입
  MyData,
  // payload creator의 첫번째 파라미터 타입
  number,
  {
    // thunkapi 옵선의 타입
    dispatch: AppDispatch
    state: State
    rejectValue: {
      msg: string
    }
  }
>('users/fetchById', async (userId, thunkApi) => {
  const response = await fetch(`https://reqres.in/api/users/${userId}`)

  if(response.status === 400) {
  	return thunkApi.rejectWithValue({msg : '~~'})
  }
  return (await response.json()) as MyData
})

 

thunkApi의 옵션에 관한 내용은 공식문서를 참고하는 것이 좋을 것 같다.(아직 나도 잘 모르겠다.)

https://redux-toolkit.js.org/usage/usage-with-typescript

 

 

useAppSelector 처럼 사전 타입화된 형태로 사용할 수 있는 방법도 있다.

 

const createAppAsyncThunk = createAsyncThunk.withTypes<{
  state: RootState;
  dispatch: AppDispatch;
  rejectValue: { message: string };
}>();

 

이제 createAppAsyncThunk를 사용하면 된다.

 

 

확실히 복잡하고 thunkApi의 다른 옵션들은 아직 잘 모르겠다.  공부해서 추가적으로 정리해보겠다.

'concept' 카테고리의 다른 글

변신하는 Form  (1) 2023.10.18
CSS 프레임워크로 atomic design pattern 적용하기  (0) 2023.09.02
flex-item  (0) 2023.08.30
MVVM 패턴  (0) 2023.08.10
module css, classnames로 조건부 스타일링  (0) 2023.08.07