리덕스에서는 기본적으로 액션 객체를 디스패치 한다.
하지만 thunk 미들웨어를 사용하면 객체 대신 함수를 생성하는 액션 생성함수를 작성할 수 있게 해준다.
이러한 동작 방식을 활용하여 Redux에서 비동기적인 프로그래밍을 구현할 수 있다.
function increment() {
return {
type: INCREMENT_COUNTER
};
}
예전에는 액션 생성함수에서 위와 같은 type을 가지는 객체를 생성해주었다면
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function incrementAsync() {
return dispatch => { // dispatch를 가지는 함수를 리턴한다.
setTimeout(() => {
// 1초 뒤 dispatch 한다.
dispatch(increment());
}, 1000);
};
}
redux-thunk를 사용하면 위의 incrementAsync 같은 함수를 생성하는 액션 생성함수를 만들 수 있다.
만약, 리턴하는 함수에서 dispatch, getState
를 파라미터로 받게 한다면
아래와 같이 스토어의 상태에도 접근할 수 있다.
따라서, 현재의 스토어 상태 값에 따라 액션이 dispatch 될 지, 무시될 지 정해줄 수 있다.
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0 ) {
return;
}
dispatch(increment());
};
}
보통의 액션 생성자는 그냥 하나의 액션 객체를 생성할 뿐이지만,
redux-thunk
를 통해 만든 액션 생성자는 그 내부에서 여러가지 작업을 할 수 있다.
예를 들면 네트워크 요청을 하거나, 스토어에 접근해서 state를 읽어오거나, 다른 액션을 디스패치 하는 등 액션을 여러 번 디스패치 할 수도 있다.
import axios from 'axios';
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
// createAsyncThunk는 비동기 작업을 처리하는 액션(Action Creator)을 생성
// 첫 번째 인수로 타입을, 두 번째 인수로 액션이 실행됐을 때 처리되어야 할 작업을 함수로 전달
export const fetchData = createAsyncThunk('FETCH_DATA', async () => {
try {
const response = await axios.get('http://localhost:8000')
return response.data;
} catch (error) {
console.error(error);
}
});
// 비동기 작업의 3가지 상태 (pending, fulfilled, rejected)
export const rootReducer = createSlice({
name: 'Data',
initialState: {
data: [],
status: 'Welcome'
},
reducers: { // 동기적인 작업을 수행할 때 사용 (Redux-toolkit이 액션 생성자를 자동으로 생성해 줌)
// omit reducer cases
},
extraReducers: (builder) => { // 비동기적인 작업을 수행할 때 사용 (액션 생성자를 자동으로 만들어주지 못하기 때문에 extraReducers 안에 직접 정의함)
builder
.addCase(fetchData.pending, (state, action) => {
// 데이터 통신 중일 때
// state.status = 'Loading' 또는 다음과 같은 형태로 반환
return {
...state,
status: 'Loading',
}
})
.addCase(fetchData.fulfilled, (state, action) => {
// 데이터 통신에 성공했을 때
return {
...state,
data: [ ...action.payload ],
status: 'Completed',
}
})
.addCase(fetchData.rejected, (state, action) => {
// 데이터 통신에 실패했을 때
return {
...state,
status: 'Failed',
}
})
},
})
// app.ts
dispatch(fetchData())