Redux Toolkit 에서 createAsyncThunk 사용하여 비동기 처리하기

Yeojin Choi·2022년 5월 24일
2

thunk 란?

Thunk 란?
특정 작업을 나중에 할 수 있도록 미루기 위해 함수 형태로 감싼 것

예를 들어 주어진 파라미터에 1을 더하는 작업이 있다면

// 즉시 실행
const addOne = x => x + 1;
addOne(1); 

// 나중에 실행
const addOneThunk = x => () => addOne(x);
setTimeOut(()=>{
  	addOneThunk(1) // 1초 뒤 실행
}, 1000);

addOne() 이라는 작업을 1초 뒤에 실행할 수 있도록 addOneThunk 라는 함수로 감쌌는데 이 함수를 thunk 라고 한다.

redux-thunk & createAsyncThunk

  • Redux 에서 비동기 처리를 위해서는 redux-thunk 미들웨어를 설치하고 추가해주여야 한다.
  • Redux Toolkit 에는 기본적으로 thunk middleware 가 미들웨어에 추가되어있기 때문에 redux-thunk 미들웨어를 설치할 필요가 없다.
  • createAsyncThunk API 를 통해 fulfilled, rejected, pending 3가지 상태에 대해 reducer 를 작성할 수 있다.
  • createAsyncThunkredux-saga 에서만 사용할 수 있던 기능(이미 호출한 API 요청 취소하기 등)도 사용할 수 있다.

thunk 생성하기

createAsyncThunk(typePrefix: string, payloadCreator: AsyncThunkPayloadCreator, options?: AsyncThunkOptions): AsyncThunk
// ex
export const getRecentPosts = createAsyncThunk('posts/getRecentPosts', async () => {
  const response = await axios({ baseURL: API_HOST, url: '/recentPosts' });
  return (await response.data.json()) as PartialPost[];
});

Role

  • parameter 로 받은 typePrefix 를 기반으로 프로미스 라이프사이클 액션 타입을 생성한다.
  • parameter 로 받은 promise callback 을 실행하고, promise 를 기반으로 라이프 사이클 액션을 dispatch 하는 action creator 를 반환한다.
  • createAsyncThunk 입장에서는 사용자가 데이터를 어떻게 처리할 지 모르기 때문에 reducer 함수를 생성하지는 않는다.

Parameter

typePrefix

  • 액션 타입 문자열
  • 이 값을 기반으로 pending / fulfilled / rejected 가 붙은 액션 타입이 생성되어 reducer 로 들어온다.
    - pending: posts/getRecentPosts/pending
    - fulfilled: posts/getRecentPosts/fulfilled
    - rejected: posts/getRecentPosts/rejected

payloadCreator

  • 프로미스를 반환하는 비동기 함수

Return Value

createAsyncThunkaction creator 를 반환한다.

  • getRecentPosts.pending : promise 가 pending 되었을 때 발생하는 액션을 디스패치하는 action creator
  • getRecentPosts.fulfilled : promise 가 fulfilled 되었을 때 발생하는 액션을 디스패치하는 action creator
  • getRecentPosts.rejected : promise 가 rejected 되었을 때 발생하는 액션을 디스패치하는 action creator

thunk 사용하기

slice

createSlice 의 extraReducers 함수를 이용해, builder 에 각 상황에 대한 리듀서를 추가해주어야 한다.
fulfiled 시 데이터는 action.payload 로 들어오고, rejectedaction.error 로 들어오며 payload 는 undefined 이다.

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

Component

dispatch(createAsyncThunk(..)) 를 호출하여 Component 에서 액션을 dispatch 할 수 있다.

const RecentPostsPage = () => {
  const dispatch = useDispatch();
  const { posts, loading } = useAppSelector(state => state.postsState);

  useEffect(() => {
    dispatch(getRecentPosts());
  }, [dispatch]);
  
  return (
    <>
      <PostCardGrid posts={posts} />
      {loading !== 'idle' && <PostCardGridSkeleton />}
    </>
  );
};

위의 예시 상황에서는
1. 컴포넌트가 마운트 되면 getRecentPosts 라는 action creator 함수를 실행한다.
1. action creator 가 pending 액션을 디스패치한다.
2. action creator 가 parameter 로 받은 payloadCreator 콜백을 호출하고, 프로미스가 반환되기를 기다린다
3. 프로미스가 반환되면,

  • 프로미스가 이행된 상태라면, action.payload를 fulfilled 액션에 담아 디스패치한다.
  • 프로미스가 거부된 상태라면, rejected 액션을 디스패치한다.

참고
Redux Toolkit 공식 문서
Usage With Typescript

profile
프론트가 좋아요

1개의 댓글

comment-user-thumbnail
2023년 2월 18일

설명이 정말 깔끔해서 이해가 잘 됩니다. 감사합니다!!

답글 달기