Async Logic과 Middleware에 대해서 알아보자!
1. Async 로직
동기(sync) 방식에서는 리듀서에서 액션에 대한 처리가 모두 끝나고,
Redux store에 들어있는 State가 업데이트 된 이후에 프로그램의 흐름이 이어진다.
반면, 비동기(Async) 방식에서는 Reducer에서 액션에 대한 처리가 모두 끝나기 전에,
프로그램의 흐름은 계속 이어지고, Reducer에서 액션에 대한 처리가 끝난 이후에 프로그램에 알려주는 형태이다.
Redux에서는 기본적으로 비동기 로직을 허용하지 않는다.
왜냐하면 Reduce의 규칙에서 "비동기 로직이나 Side effects(Side effects란, Reducer 외부에서 보여질 수 있는 상태나 동작을 말한다. ex. 콘솔에 로그 출력, 파일 저장 등)를 허용하지 않는다"고 했기 때문이다.
예를들면, Reducer 내에서 서버와 통신을 해서 데이터를 받아오거나 하는 등의 동작을 하면 안된다는 것이다.
이렇게 허용하지 않는 이유는 Reducer 외부와 내부를 철저하게 분리하고, return 값과 관련이 없는 동작을 막음으로써 완벽한 pure function이 되게끔 하기 위함이다.
그리고 Reducer가 완벽한 pure function이 됨으로써, 상태변화를 예측하기가 쉬워지고, 예상치 못한 상태의 변화를 막음으로써 예상치 못한 결과가 발생하는 것을 막을 수 있게 된다.
2. 미들웨어(Middleware)
Redux에서는 기본적으로 비동기 로직을 허용하지 않는다고 했는데,
미들웨어(Middleware)를 사용하면, 비동기 작업(예: 네트워크 요청)을 처리할 수 있다.
미들웨어는 "Redux에 원하는 기능을 추가할 수 있게 해주는 함수"이다.
미들웨어를 사용하면 Redux store에서 action이 처리될때 함께 작동하길 원하는 코드를 끼워넣을 수 있게 된다.
비동기 로직을 처리해주는 미들웨어 중에서 대표적인 것이 redux-thunk와 redux-saga이다.
리덕스의 Flux 패턴에서 맨 처음 액션을 디스패치 하게 되면
리듀서에서 해당 액션에 대한 정보를 바탕으로 스토어의 상태값을 바꾸게 되는데,
이 때 미들웨어를 사용하면 액션이 스토어에서 상태값을 바꾸기 전에 특정 작업들을 수행할 수 있다.
예를 들면, 다음과 같은 역할을 수행한다.
- 특정 조건에 따라 액션 수행 여부를 판단
-액션을 콘솔에 출력하거나 서버 쪽에 로깅함
- 액션이 디스패치 되었을 때, 데이터를 수정하여 리듀서에게 전달
- 비동기적인 작업을 수행
3. Redux에서 비동기 작업을 처리할 때 실제 데이터의 흐름
비동기 작업을 요청하는 액션이 디스패치(dispatch) 된다.
디스패치 된 액션을 미들웨어에서 처리한다.
미들웨어에서 비동기 작업이 완료되면 완료 액션(Action)을 디스패치 한다.
이 때, 비동기 작업의 성공여부에 따라 액션 타입에 fulfilled 또는 rejected를 작성한다.
비동기 작업의 완료 액션을 처리하는 리듀서(Reducer)를 통해서 store의 state를 업데이트 한다.
4. Todo 프로젝트에 Async Function Middleware 적용하기
*참고 (아래 게시글에서 이어지는 코드입니다.)
https://dev-ini.tistory.com/286
[Redux] Redux로 프로젝트 시작하는 법 (간단한 Todo 프로젝트 만들어보기)
Redux랑 React 연동해서 간단한 프로젝트를 만들어볼 것이다. * 필자는 Vite를 사용하여 React 개발 환경을 설정하였다. (참고 ) https://dev-ini.tistory.com/58)* Toolkit은 사용하지 않은 버전이다. 추후 학
dev-ini.tistory.com
(1) redux폴더 안에 middlewares 폴더를 만들고, 그 안에 asyncFunctionMiddleware.js 파일을 만든다.
asyncFunctionMiddleware.js
const asyncFunctionMiddleware = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return next(action);
}
export default asyncFunctionMiddleware;
(2) 만든 middleware를 store에 적용한다.
src/redux/store.js
import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "./reducers";
import asyncFunctionMiddleware from "./middlewares/asyncFunctionMiddleware";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(asyncFunctionMiddleware))
);
export default store;
(3) src > redux > containers > TodoAppContainer.js 수정
TodoAppContainer.js
import { connect } from "react-redux";
import {
addTodo as addTodoActionCreator,
removeTodo as removeTodoActionCreator,
removeAll as removeAllActionCreator
} from "../actions/todoAction";
import TodoApp from "../../components/TodoApp";
function mapStateToProps(state) {
return {
todoItems: [...state.todo, ...(state.fetchTodos?.data || [])],
};
}
function mapDispatchToProps(dispatch) {
return {
addTodo: (text) => {
dispatch(addTodoActionCreator(text));
},
removeTodo: () => {
dispatch(removeTodoActionCreator());
},
removeAll: () => {
dispatch(removeAllActionCreator());
},
triggerAsyncFunction: (asyncFunction) => { // 추가
dispatch(asyncFunction);
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
(4) src/components/TodoApp.jsx에 적용
TodoApp.jsx
import { useState } from "react";
function TodoApp(props) {
const {
todoItems,
addTodo,
removeTodo,
removeAll,
triggerAsyncFunction,
} = props;
const [newTodo, setNewTodo] = useState("");
return (
<div>
<h3>오늘 할 일</h3>
{/* 할일 목록 */}
<ul>
{todoItems.map((todoItem, index) => {
return <li key={index}>{todoItem}</li>;
})}
</ul>
<div>
<input
value={newTodo}
onChange={(event) => {
setNewTodo(event.target.value);
}}
/>
<button
onClick={() => {
addTodo(newTodo);
setNewTodo("");
}}
>
할 일 추가
</button>
<button onClick={removeTodo}>할 일 삭제</button>
<button onClick={removeAll}>모두 삭제</button>
</div>
<button
onClick={() => {
triggerAsyncFunction((dispatch, getState) => {
console.log(`비동기 함수 실행`, getState());
new Promise((resolve) => {
setTimeout(resolve, 3000);
})
.then(() => {
console.log(`비동기 함수 성공`, getState());
})
.finally(() => {
console.log(`비동기 함수 종료`, getState());
});
});
}}
>
비동기 함수 테스트
</button>
</div>
);
}
export default TodoApp;
실행 결과
'Libraries > Redux' 카테고리의 다른 글
[Redux] redux-actions (1) | 2025.02.03 |
---|---|
[Redux] Ducks Pattern (0) | 2025.01.31 |
[Redux] Redux로 프로젝트 시작하는 법 (간단한 Todo 프로젝트 만들어보기) (1) | 2025.01.30 |
[Redux] Redux와 React를 연동하기 (1) | 2025.01.18 |
[Redux] Reducer (0) | 2025.01.12 |