리덕스를 사용하는 어플리케이션에서 비동기 작업을 처리 할 때 가장 기본적인 방법으로는 redux-thunk 라는 미들웨어를 사용하는것입니다.
이 미들웨어는 비동기 작업을 다룹니다. 이를 사용하여 비동기 작업을 관리하는건 매우 직관적이고 간단하다.
// dispatch 와 reducer사이에 동작하는게 미들웨어다.
// 매개변수3개가들어가는 함수, 밑에처럼 사이사이에 함수를 실행할 필요가 없는경우
const firstMiddleware = (store) => (dispatch) => (action) => { //미들웨어 예시1
console.log('first-middleware-로깅 시작', action);
dispatch(action); //dispatch + console기능이 추가된 미들웨어임
console.log('first-middleware-로깅 끝');
};
// 들어오는 객체가 함수면 비동기처리를 하고 , 객체면 동기처리를 수행한다
const thunkMiddleware = (store) => (dispatch) => (action) => {/
if (typeof action === 'function') { //함수면 -> 비동기로 처리
return action(store.dispatch, store.getState); //함수실행
}
return dispatch(action); //객체면->동기처리
};
액션생성함수도 객체,함수에 따라 구분해서 만든다.
비동기 액션함수일경우 return값을 객체가아닌 함수를 리턴한다. setTimeout같은 비동기처리안에서 동기적인 action을 dispatch하는 구조다.
//비동기 액션(함수) creator
const logIn = (data) => {
return (dispatch, getState) => { // async action -> 동기액션크리에이터와 다르게 함수를 리턴
dispatch(logInRequest(data)); //로그인요청
try {
setTimeout(() => {
dispatch(logInSuccess({ //로그인성공 // 비동기작업 환경에서 동기작업을 함
userId: 1,
nickname: 'seok'
}));
}, 2000);
} catch (e) {
dispatch(logInFailure(e));//로그인실패
}
};
};
//동기 액션크리에이터
const logInRequest = (data) => {
return {
type: 'LOG_IN_REQUEST',
data,
}
};
//동기 액션크리에이터
const logInSuccess = (data) => {
return {
type: 'LOG_IN_SUCCESS',
data,
}
};
//동기 액션크리에이터
const logInFailure = (error) => {
return {
type: 'LOG_IN_FAILURE',
error,
}
};
//동기 액션크리에이터
const logOut = () => {
return { // action
type: 'LOG_OUT',
};
};
store.dispatch(logIn({ //로그인요청(비동기처리->thunk작동)
id: 1,
name: 'seok',
admin: true,
}));
// index.js
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import logger from 'redux-logger';
import ReduxThunk from 'redux-thunk';
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(ReduxThunk, logger)) // logger 를 사용하는 경우, logger가 가장 마지막에 와야합니다.
); // 여러개의 미들웨어를 적용 할 수 있습니다.
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// actions//counter.js
// 액션 타입
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
// 액션 생성 함수
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
// getState를 쓰지 않는다면 굳이 파라미터로 받아올 필요 없습니다.
export const increaseAsync = () => dispatch => {
setTimeout(() => dispatch(increase()), 1000);
};
export const decreaseAsync = () => dispatch => {
setTimeout(() => dispatch(decrease()), 1000);
};
// 초깃값 (상태가 객체가 아니라 그냥 숫자여도 상관 없습니다.)
const initialState = 0;
export default function counter(state = initialState, action) {
switch (action.type) {
case INCREASE:
return state + 1;
case DECREASE:
return state - 1;
default:
return state;
}
}
// CounterContainer.js
import React from 'react';
import Counter from '../components/Counter';
import { useSelector, useDispatch } from 'react-redux';
import { increaseAsync, decreaseAsync } from '../modules/counter';
function CounterContainer() {
const number = useSelector(state => state.counter);
const dispatch = useDispatch();
const onIncrease = () => {
dispatch(increaseAsync());
};
const onDecrease = () => {
dispatch(decreaseAsync());
};
return (
<Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
);
}
export default CounterContainer;