리덕스 미들웨어
액션이 디스패치 된 다음, 리듀서에서 해당 액션을 받아와서 업데이트하기 전에 추가적인 작업을 할 수 있다. 주로 비동기 작업을 처리할때 사용한다.
예시)
- 특정 조건에 따라 액션 무시하기
- 액션을 콘솔에 출력하거나 서버쪽에 로깅하기
- 액션이 디스패치 되었을때 이를 수정해서 리듀서에게 전달되게 하기
- 특정 액션이 발생했을 때 이에 기반해 다른 액션이 발생하게 하기
- 특정 액션이 발생했을 때 특정 자바스크립트 함수 실행하기
미들웨어 만들어보고 이해하기
1) 리덕스 미들웨어의 템플릿
리덕스 미들웨어를 만들때에는 다음과 같은 형태의 템플릿을 사용한다.
const middleware = store => next => action => {
// 하고싶은 작업...
}
// 화살표가 여러개지만 function 을 사용하여 작성한다면 다음과 같이 이해할수있다
function middleware(store) {
return function (next) {
return function (action) {
// 하고 싶은 작업...
};
};
};
- store : 리덕스 스토어 인스턴스. dispatch, getState, subscribe 등 내장함수가 들어있다.
- next : 액션을 다음 미들웨어에게 전달하는 함수. next(action) 과 같은 형태로 사용한다. 만약 다음 미들웨어가 없다면 리듀서에게 액션을 전달한다. next를 호출하지 않을 경우 액션이 무시되어 리듀서에게로 전달되지 않는다.
- action : 현재 처리하고 있는 액션 객체
리덕스 스토어에는 여러 개의 미들웨어를 등록할 수 있으며 새로운 액션이 디스패치 되면 첫번재로 등록한 미들웨어가 호출된다. next(action)을 호출할경우 다음 미들웨어로 액션이 넘어가고, store.dispatch를 사용하면 다른 액션을 추가적으로 발생시킬 수도 있다.
2) 미들웨어 직접 작성해보기
src/middlewares/myLogger.js
const myLogger = store => next => action => {
console.log(action); // 액션 출력
const result = next(action); // 다음 미들웨어 (또는 리듀서)에게 액션을 전달
return result; // 여기서 ㄴ반환하는 값은 dispatch(action)의 결과물이 된다. 기본값은 undefined;
};
export default myLogger;
3) 미들웨어 적용하기
스토어에 미들웨어를 적용할때는 applyMiddleware 사용. ⇒ src/index.js 수정
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import myLogger from './middlewares/myLogger';
const root = ReactDOM.createRoot(document.getElementById('root'));
const store = createStore(rootReducer, applyMiddleware(myLogger));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
reportWebVitals();
콘솔창에 액션의 이름이 출력된다
4) 미들웨어 수정하기
src/middlewares/myLogger.js 수정
액션이 리듀서까지 전달되고 난 후의 새로운 상태를 확인하기
const myLogger = store => next => action => {
console.log(action); // 액션 출력
const result = next(action); // 다음 미들웨어 (또는 리듀서)에게 액션을 전달
// 업데이트 이후의 상태 조회
console.log('\\t', store.getState()); // \\t는 탭 문자
return result; // 여기서 ㄴ반환하는 값은 dispatch(action)의 결과물이 된다. 기본값은 undefined;
};
export default myLogger;
액션과 액션이 전달된 이후 스토어의 상태를 확인할 수 있다
⇒ 위와 같이 리덕스 관련 값들을 콘솔에 로깅하기 쉽게 해주는 미들웨어 ⇒ redux-logger
redux-thunk
1) 소개
redux-thunk : 리덕스에서 비동기 작업을 처리 할때 가장 많이 사용하는 미들웨어.
액션 객체가 아닌 함수를 디스패치 할 수 있다.
함수를 디스패치 할때는 해당 함수에서 dispatch와 getState를 파라미터로 받아와야한다. 이 함수를 만들어주는 함수가 thunk이다.
thunk 의 사용 예시 1
const getComments = () => (dispatch, getState) => {
// 이 안에서는 액션을 dispatch 할 수도 있고
// getState를 사용하여 현재 상태도 조회 할 수 있습니다.
const id = getState().post.activeId;
// 요청이 시작했음을 알리는 액션
dispatch({ type: 'GET_COMMENTS' });
// 댓글을 조회하는 프로미스를 반환하는 getComments 가 있다고 가정해봅시다.
api
.getComments(id) // 요청을 하고
.then(comments => dispatch({ type: 'GET_COMMENTS_SUCCESS', id, comments })) // 성공시
.catch(e => dispatch({ type: 'GET_COMMENTS_ERROR', error: e })); // 실패시
};
thunk에서 async/await를 사용하는 예시
const getComments = () => async (dispatch, getState) => {
const id = getState().post.activeId;
dispatch({ type: 'GET_COMMENTS' });
try {
const comments = await api.getComments(id);
dispatch({ type: 'GET_COMMENTS_SUCCESS', id, comments });
} catch (e) {
dispatch({ type: 'GET_COMMENTS_ERROR', error: e });
}
}
2) redux-thunk 설치 및 적용하기
$ npm add redux-thunk
redux-thunk를 index.js에서 불러와서 applyMiddlewares를 적용
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import logger from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import ReduxThunk from 'redux-thunk';
const root = ReactDOM.createRoot(document.getElementById('root'));
const store = createStore(
rootReducer,
// logger를 사용하는 경우 logger가 맨 마지막에 위치해야함
composeWithDevTools(applyMiddleware(ReduxThunk, logger))
// 여러개의 미들웨어 적용 가능
);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
reportWebVitals();
3) 카운터 딜레이하기
thunk 함수를 만들고 setTimeout을 사용해서 액션이 디스패치되는 것을 1초씩 딜레이시키는 예제
modules/counter.js
// 액션 타입
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
// 액션 생성 함수
export const increase = () => ({type: INCREASE});
export const decrease = () => ({type: DECREASE});
// getState를 쓰지 않는다면 굳이 파라미터로 받아올 필요 X
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;
}
}
container/CounterContainer.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { decreaseAsync, increaseAsync } 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;
*******************************************************
참고
https://react.vlpt.us/redux-middleware/03-logger-and-devtools.html
https://react.vlpt.us/redux-middleware/04-redux-thunk.html
'study > TIL' 카테고리의 다른 글
23.03.02 ~ 03 - 웹 접근성, WAI-ARIA (0) | 2023.03.02 |
---|---|
23.02.28 - 웹표준, SEO (0) | 2023.02.28 |
23.02.24 - Redux (0) | 2023.02.24 |
23.02.23 - 상태 관리 (0) | 2023.02.23 |
23.02.20 - Component Driven Development, Styled Components, Storybook, useRef (0) | 2023.02.20 |