이전포스팅에서 이어서 이번 포스팅에서는 리덕스에서 미들웨어의 기본 개념과 Redux Thunk에 대해서 정리해보려 한다.
목차
- 미들웨어(Middleware) 란?
- 미들웨어 함수 생성
- 비동기처리를 위한 미들웨어 : Redux Thunk
1. 미들웨어(Middleware) 란?
Redux 미들웨어를 간단하게 요약하면 action을 dispatch로 전달하고 reducer에 도달하기 이전에 지정된 작업을 실행할 수 있게 해주는 중간자이다. reducer가 dispatch 받은 액션을 처리하기 전에 할 수 있는 작업들은 다양한데 예를 들면 로깅, API 비동기 작업, 라우팅 등이 있다.
2. 미들웨어 함수 생성
미들웨어의 사용 예시를 위해 리덕스 사용시 console 로그를 찍는 간단한 미들웨어 함수를 작성하였다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { applyMiddleware, createStore } from 'redux';
import rootReducer from './reducers';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
// 미들웨어 함수 작성
const loggerMiddleware = (store: any) => (next: any) => (action: any) => {
console.log('store', store);
console.log('action', action);
next(action);
};
const secondLoggerMiddleware = (store: any) => (next: any) => (action: any) => {
console.log('두번째 미들웨어');
next(action);
};
//미들웨어 함수를 applyMiddleware 함수 인자로 사용
const middleware = applyMiddleware(loggerMiddleware, secondLoggerMiddleware);
//createStore에 applyMiddleware를 인자로 사용
const store = createStore(rootReducer, middleware);
const render = () =>
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
render();
store.subscribe(render);
미들웨어 함수 사용을 위해 위와 같이 applyMiddleware 함수를 import하고 로그를 출력하는 미들웨어 함수를 작성한 뒤 applyMiddleware 함수의 인자로 사용한다. 미들웨어 함수는 여러개를 사용할 수 있으며 위와 같이 여러개의 미들웨어 함수를 작성후 applyMiddleware 함수에 인자로 모두 넣어주면 된다.
위의 loggerMiddleware 함수를 풀어쓰면 아래와 같다.
const loggerMiddleware = function (store) {
return function (next){
return funtcion (action){
console.log('store', store);
console.log('action', action);
next(action);
}
}
}
- store : getState, dispatch 함수를 제공
1) getState : 현재 redux store에 저장된 상태값들에 접근가능
2) dispatch : 매개변수를 넣고 실행하여 다시 action을 전달함. 이때 첫번째 미들웨어가 다시 실행되어 작업을 처리한다.
- next : 액션을 다음 미들웨어로 전달하는 함수이다. 만약 다음 미들웨어가 없다면 reducer로 전달한다.
- action : 현재 처리하고 있는 action 객체의 정보가 들어있다.
3. 비동기처리를 위한 미들웨어 : Redux Thunk
Redux Thunk는 리덕스를 사용하는 앱에서 비동기 작업을 수행할 때 많이 사용하는 방법이다. 리덕스에서 상태값을 변화시키기 위해 useDispatch 함수를 사용하여 액션 객체를 생성하여 reducer 에게 전달하게 되는데 Redux Thunk를 사용하면 Thunk 함수를 dispatch하고 사전에 수행해야 할 작업을 처리 후 결과 값을 함수 내부에서 다시 dispatch 하여 reducer에게 전달할 수 있다.
아래예시코드를 확인해보자.
#redux-thunk 설치
npm install redux-thunk --save
//src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { applyMiddleware, createStore } from 'redux';
import rootReducer from './reducers';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
const loggerMiddleware = (store: any) => (next: any) => (action: any) => {
console.log('첫번째 미들웨어');
console.log('store', store);
console.log('action', action);
next(action);
};
const middleware = applyMiddleware(thunk, loggerMiddleware);
const store = createStore(rootReducer, middleware);
const render = () =>
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
render();
store.subscribe(render);
Redux Thunk를 사용하기 위해서 위 명령어로 redux-thunk를 설치하고 'redux-thunk'에서 thunk를 import 한 후 applyMiddleware 함수에 인자로 thunk를 넣어준다.
실행순서는 아래와 같다.
//App.tsx
import React, { useEffect, useState } from 'react';
import './App.css';
import { useDispatch } from 'react-redux';
import { useAppSelector } from './reducers/hooks';
import { fetchPosts } from './actions/posts';
interface Post {
userId: number;
id: number;
title: string;
}
function App() {
const dispatch = useDispatch();
const posts: Post[] = useAppSelector((state) => state.posts);
useEffect(() => {
dispatch(fetchPosts()); // thunk 함수를 dispatch
}, []);
return (
<div className='App'>
<div className='todoApp'>
<ul>
{posts.map((post, idx) => (
<li key={idx}>{post.title}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
1) App.tsx에서 렌더링 후 useEffect가 실행되어 thunk함수인 fetchPosts 함수를 dispatch 한다.
//actions/posts.tsx
import axios from 'axios';
// dispatch 할 thunk 함수
export const fetchPosts = (): any => async (dispatch: any, getState: any) => {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
dispatch({ type: 'FETCH_POSTS', payload: response.data });
};
2) fetchPosts 함수가 실행되면서 비동기로 데이터를 가져온 후 'FETCH_POSTS' 액션을 dispatch 해주고 payload에 비동기로 가져온 데이터를 같이 전달한다.
//reducers/posts.tsx
enum ActionType {
FETCH_POSTS = 'FETCH_POSTS',
DELETE_POSTS = 'DELETE_POSTS',
}
interface Post {
userId: number;
id: number;
title: string;
}
interface Action {
type: ActionType;
payload: Post[];
}
const posts = (state: string[] = [], action: Action) => {
switch (action.type) {
case 'FETCH_POSTS':
return [...state, ...action.payload];
default:
return state;
}
};
export default posts;
3) posts 리듀서에서 action.type이 'FETCH_POSTS' 인 로직이 실행되고 action.payload에 thunk함수에서 비동기로 가져온 데이터를 확인할 수 있다.
위 코드는 컴포넌트가 마운트 될때 fetchPost함수를 dispatch하여 무료 rest api 를 제공하는 jsonplaceholder 에서 json data 더미를 받아와서 다시 dispatch 해주는 코드이다. 위와 같이 비동기로 처리할 작업을 함수내에 작성하는데 dispatch와 getState를 파라미터로 받아야 함수 내부에서 상태값에 접근하거나 비동기 처리 작업후 결과값을 다시 dispatch 할 수 있다.