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.)