Redux 미들웨어 개념
들어가며
미들웨어 개념이 정~말 다루는게 복잡했다. 개념 자체가 엄청 어려운건 아닌데 코드 구조가 눈에 안익어서 어렵다고 느꼈다. 특히 최적화와 추상화를 하니까 안그래도 어색한데 더 보기가 힘들었다. 이번 프로젝트에 적용하면서 튜토리얼의 레벨만큼은 따라가고 싶다.
미들웨어 템플릿
- 첫번째
store
는 리덕스 스토어의 인스턴스다. 이 안에 dispatch
,getState
,subscribe
내장 함수들이 들어있다.
- 두번째
next
는 액션을 다음 미들웨어에게 전달하는 함수다. next(action)
이런 형식으로 사용한다. 만약 미들웨어가 없으면 리듀서에게 전달한다. next
가 생략될 경우 리듀서로 전달하지 않는다.
- 세번째
action
은 현재 처리하고 있는 액션 객체다.
- 만약 미들웨어에서
store.dispatch
를 사용하면 다른 액션을 추가적으로 발생시킬 수도 있다.
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;
store
는 리덕스 스토어의 인스턴스다. 이 안에 dispatch
,getState
,subscribe
내장 함수들이 들어있다. next
는 액션을 다음 미들웨어에게 전달하는 함수다. next(action)
이런 형식으로 사용한다. 만약 미들웨어가 없으면 리듀서에게 전달한다. next
가 생략될 경우 리듀서로 전달하지 않는다. action
은 현재 처리하고 있는 액션 객체다.store.dispatch
를 사용하면 다른 액션을 추가적으로 발생시킬 수도 있다.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;
미들웨어 적용하기
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import myLogger from './middlewares/myLogger';
const store = createStore(rootReducer, applyMiddleware(myLogger)); <<<<
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
redux-thunk
redux-thunk는 리덕스에서 비동기 작업을 처리할 때 가장 많이 사용하는 미들웨어다.
이 미들웨어를 사용하면 액션 객체가 아닌 함수를 디스패치 할 수 있다.
해당 함수에서 dispatch
와 getState
를 파라미터로 받아와야 한다.
이 함수를 만들어주는 함수를 thunk라고 부른다.
modules/counter.js
// 액션 타입
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
// 액션 생성 함수
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
// getState를 쓰지 않는다면 굳이 파라미터로 받아올 필요 없습니다.
export const increaseAsync = () => dispatch => { // <<<<<< thunk 함수 생성
setTimeout(() => dispatch(increase()), 1000);
};
export const decreaseAsync = () => dispatch => { // <<<<<< thunk 함수 생성
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;
}
}
containers/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;
redux-thunk로 프로미스 다루기
리팩토링한 결과를 보는 것 보다 이 코드가 초심자 입장에서는 가독성이 더 좋다.
비동기 요청 시작(LOADING), 성공(SUCCESS), 실패(ERROR) 상태로 나누고 그에 맞는 객체를 반환한다.
추상화 과정은 리듀서마다 중복되는 과정을 간략하게 변환한다.
modules/posts.js
import * as postsAPI from '../api/posts'; // api/posts 안의 함수 모두 불러오기
/* 액션 타입 */
// 포스트 여러개 조회하기
const GET_POSTS = 'GET_POSTS'; // 요청 시작
const GET_POSTS_SUCCESS = 'GET_POSTS_SUCCESS'; // 요청 성공
const GET_POSTS_ERROR = 'GET_POSTS_ERROR'; // 요청 실패
// 포스트 하나 조회하기
const GET_POST = 'GET_POST';
const GET_POST_SUCCESS = 'GET_POST_SUCCESS';
const GET_POST_ERROR = 'GET_POST_ERROR';
// thunk 를 사용 할 때, 꼭 모든 액션들에 대하여 액션 생성함수를 만들 필요는 없습니다.
// 그냥 thunk 함수에서 바로 액션 객체를 만들어주어도 괜찮습니다.
export const getPosts = () => async dispatch => {
dispatch({ type: GET_POSTS }); // 요청이 시작됨
try {
const posts = await postsAPI.getPosts(); // API 호출
dispatch({ type: GET_POSTS_SUCCESS, posts }); // 성공
} catch (e) {
dispatch({ type: GET_POSTS_ERROR, error: e }); // 실패
}
};
// thunk 함수에서도 파라미터를 받아와서 사용 할 수 있습니다.
export const getPost = id => async dispatch => {
dispatch({ type: GET_POST }); // 요청이 시작됨
try {
const post = await postsAPI.getPostById(id); // API 호출
dispatch({ type: GET_POST_SUCCESS, post }); // 성공
} catch (e) {
dispatch({ type: GET_POST_ERROR, error: e }); // 실패
}
};
const initialState = {
posts: {
loading: false,
data: null,
error: null
},
post: {
loading: false,
data: null,
error: null
}
};
export default function posts(state = initialState, action) {
switch (action.type) {
case GET_POSTS:
return {
...state,
posts: {
loading: true,
data: null,
error: null
}
};
case GET_POSTS_SUCCESS:
return {
...state,
posts: {
loading: true,
data: action.posts,
error: null
}
};
case GET_POSTS_ERROR:
return {
...state,
posts: {
loading: true,
data: null,
error: action.error
}
};
case GET_POST:
return {
...state,
post: {
loading: true,
data: null,
error: null
}
};
case GET_POST_SUCCESS:
return {
...state,
post: {
loading: true,
data: action.post,
error: null
}
};
case GET_POST_ERROR:
return {
...state,
post: {
loading: true,
data: null,
error: action.error
}
};
default:
return state;
}
}
API 재로딩 문제 해결
재로딩을 해결하는 방법은 두 가지가 있다.
첫번째는 만약 데이터가 이미 존재한다면 요청을 보내지 않는다.
function PostListContainer() {
const { data, loading, error } = useSelector(state => state.posts.posts);
const dispatch = useDispatch();
// 컴포넌트 마운트 후 포스트 목록 요청
useEffect(() => {
if (data) return;
dispatch(getPosts());
}, [data, dispatch]);
두번째는 로딩을 새로하긴 하는데 로딩중...
을 띄우지 않는다.
이전과 다른 데이터를 받으면 그 값으로 변환해서 출력한다.
export const handleAsyncActions = (type, key, keepData = false) => {
const [SUCCESS, ERROR] = [`${type}_SUCCESS`, `${type}_ERROR`];
return (state, action) => {
switch (action.type) {
case type:
return {
...state,
[key]: reducerUtils.loading(keepData ? state[key].data : null)
};
case SUCCESS:
return {
...state,
[key]: reducerUtils.success(action.payload)
};
case ERROR:
return {
...state,
[key]: reducerUtils.error(action.error)
};
default:
return state;
}
};
};
Author And Source
이 문제에 관하여(Redux 미들웨어 개념), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@shleecloud/Redux-미들웨어-개념저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)