❗️이 포스트는 Redux-toolkit(RTK) 개념에 의거하여 작성됐으며, 개념이 부족하다면 위 개념을 숙지 후 읽기를 추천합니다.
Thunk 는 Redux 에서 비동기 처리를 도와주는 대표적인 미들웨어 중 하나이다.
말 그대로 미들웨어기 때문에 라이브러리를 설치하여 사용해야 했지만, RTK 는 그 자체를 내포하고 있어 그냥 사용하면 된다는 장점이 있다.
Redux 는 본래 동기(Synchronous)적으로 작업이 처리 된다.
action
➡️ dispatch(action)
➡️ reducer
➡️ store
즉, 데이터의 흐름이 항상 단방향이기 때문에 쉽게 제어할 수 있다는 장점을 지닌다.
하지만, 외부 API나 기타 작업이 오래걸리는 것이 필요할 땐 동기처리가 부담이 되는 경우가 있다. 이를 위해 Redux 는 다른 상태 관리 도구와의 대표적인 차별점으로 미들웨어를 지원함으로서 비동기(Asynchronous) 작업을 지원한다.
아래와 같은 코드가 있다고 해보자.
<Button
title="+"
onPress={async () => {
const res = await axios.get('SERVER_API');
const data = await res.json();
dispatch(set(data.value));
// 위 dispatch를 풀어서 쓴 코드는 아래와 같다.
// dispatch({type: 'counterSlice/set', payload: data.value});
}}
/>
<Text>{count} | {status}</Text>
버튼을 클릭했을 때 onPress
에 의하여 안에 함수가 실행이 된다.
async/await
구문에 의하여 비동기적인 작업 처리가 되는데, SERVER_API
라는 어떠한 외부 API로부터 데이터를 받고, 이를 dispatch
하여 상태를 업데이트 하는 코딩이다.
이 코드는 사실 실행하는 데 있어 아무런 문제가 없다.
그래도 개발 관점에서 보는 문제점이란,
따라서 이를 하나의 함수로 만들어 그 것만 호출하면 얼마나 좋을까? 라는 생각에서 출발하여 미들웨어를 사용하는 이유를 곱씹어 생각하면 될 것 같다.
우리가 최종적으로 원하는 코드는 아래와 같다.
<Button
title="+"
onPress={async () => {
dispatch(asyncUpFetch());
}}
/>
<Text>{count} | {status}</Text>
asyncUpFetch()
라는 함수와 같이 알아서 비동기처리를 도와주는 함수 하나를 구현하고 싶은 것이다.
함수의 정의는 다음과 같다.
const asyncUpFetch = createAsyncThunk(
'counterSlice/asyncUpFetch',
async () => {
const res = await axios.get('SERVER_API');
const data = await res.json();
return data.value;
}
);
이는 자세히 보면 action type
이 있고, 어떤 데이터를 return 할 것인지에 대한 파라미터가 있는 것을 보면 이 전에 배운 action creator
와 완전히 동일하다!!
즉, 비동기 작업을 처리하는 action
을 만들어준다!!
따라서, action type
을 직접 적어줘야 한다.
비동기 작업에는 세 가지 상태가 있다.
이 세 가지는 따로 만드는 것이 아니라, createAsyncThunk
를 사용하면 자동으로 만들어지는 약속된 것이다.
위 세 가지 상태에 따른 reducer
가 필요할 것이다.
이는 RTK의 리듀서가 아닌 extraReducers
라는 객체에 따로 구현한다.
const counterSlice = createSlice({
name: 'counterSlice',
initialState: {
value: 0,
},
// 동기
reducers: {
up: (state, action) => {
state.value = state.value + action.payload;
},
},
// 비동기
extraReducers: (builder) => {
builder.addCase(asyncUpFetch.pending, (state, action) => {
state.status = 'Loading';
})
builder.addCase(asyncUpFetch.fulfilled, (state, action) => {
state.value = action.payload;
state.status = 'Complete';
})
builder.addCase(asyncUpFetch.rejected, (state, action) => {
state.status = 'Fail';
})
}
});
addCase
메소드를 통해 각각 세 가지 상태일 때에 따른 처리를 해준다.
❗️asyncUpFetch()
함수에서 return 값인 data.value
는 자동으로 fulfilled 상태의 action.payload
로 받는다.
createSlice
안에 존재하는 두 리듀서 객체의 차이점은 다음과 같다.
reducers
는 동기 작업, extraReducers
는 비동기 작업이다.reducers
는 action creator
를 RTK 가 자동으로 만드는 반면, extraReducers
는 수동으로 만들어야 된다.즉, 한마디로 비동기 작업은 actino creator
를 직접 만들어서 extraReducers
안에서 구현해야 한다.