프로그래밍에서 비동기 처리와 전역상태관리는 굉장히 중요합니다. 또한 그만큼 어렵습니다.
로그인한 유저 정보를 전역상태로 관리를 해야하는 경우를 예시로 본다면 로그인요청 - 서버 응답- 응답 받은 정보를 전역상태관리
와 같은 순서로 진행되게 됩니다.
현재 서버응답까지는 비동기처리와 관련한 로직, 그 이후는 전역상태관련한 로직이 필요합니다.
이러한 상황이 주어졌을 때 어떻게 구현하는 것이 좋을까요?
컴포넌트의 useEffect내에서 API호출을 하고, 응답받은 결과를 스토어에 없데이트한다.
dispatch({ type: "updateUser" , payload: { nickname: 김코딩, purchased: **패키지 }})
### 방법2
컴포넌트의 useEffect 내에서 dispatch({ type: “updateUser”})
로 액션 객체만을 보내고, user Store의 reducer 안에서 API 를 호출하고, 응답 받은 결과를 스토어에 업데이트 한다
=> reducer는 순수함수 이기 때문에 방법2는 불가능합니다.
(순수함수: 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환하는 함수)
하지만 방법1의 경우 가능하지만 컴포넌트 내에서 처리해야할 일이 너무 많습니다. (컴포넌트 내에서 처리하는 비즈니스로직이 너무 많습니다.)
이러한 단점을 보완하기 위해 사용할 수 있는 것이 바로 middleware라는 개념입니다.
액션객체를 dispatch를 한 후 바로 reducer에 보내지 않고 비동기처리, 로딩처리 등의 작업을 middleware에서 진행을 한 후 reducer로 보내게 됩니다.
또한 이 middleware를 사용하면 컴포넌트에서 비즈니스 로직도 구분해 낼 수 있습니다.
대표적인 middleware라이브러리로는 redux chunk, reudx saga, redux-observable가 있습니다.
createAsyncThunk
리덕스 툴깃에
createAsyncThunk
라는 함수가 있습니다. 이를 사용하면 다른 middleware를 사용하지 않아도 비동기통신을 편리하게 사용을 할 수 있습니다.
$ npx create-react-app my-redux-app
$ yarn add redux react-redux
$ yarn add redux-thunk
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import rootReducer from "./modules/index";
import App from "./App";
//createStore을 할 때 어떤 middleware를 사용할 것인지도 함께 명시해줍니다.
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
index.js
import { combineReducers } from "redux";
import account from "./account/account";
const rootReducer = combineReducers({ account });
export default rootReducer;
account>account.js
import { fetchUser } from "./api";
// 액션
const FETCH_USER_REQUEST = "FETCH_USER_REQUEST";
const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";
// 액션 생성 함수
export const fetchUserRequest = () => ({
type: FETCH_USER_REQUEST,
});
export const fetchUserSuccess = ({ name, email }) => ({
type: FETCH_USER_SUCCESS,
payload: { name, email },
});
export const fetchUserFailure = () => ({
type: FETCH_USER_FAILURE,
});
// TODO: thunk 함수 만들기
export const fetchUserThunk = () => {};
const initialState = {
loading: false,
name: "",
email: "",
};
export default function counter(state = initialState, action) {
switch (action.type) {
case FETCH_USER_REQUEST:
return {
...state,
loading: true,
};
case FETCH_USER_SUCCESS:
return {
loading: false,
name: action.payload.name,
email: action.payload.email,
};
case FETCH_USER_SUCCESS:
return initialState;
default:
return state;
}
}
api.js
export const fetchUser = () => {
return new Promise((resolve) => {
setTimeout(
() => resolve({ name: "hwarari", email: "hwarari@gmail.com" }),
2000
);
});
};
import { useSelector, useDispatch } from "react-redux";
import { fetchUser } from "../src/modules/account/api";
import {
fetchUserRequest,
fetchUserSuccess,
fetchUserFailure,
} from "./modules/account/account";
function App() {
const account = useSelector((state) => state.account);
const { loading, name, email } = account;
const dispatch = useDispatch();
const handleClick = async () => {
dispatch(fetchUserRequest());
try {
const res = await fetchUser();
dispatch(fetchUserSuccess({ name: res.name, email: res.email }));
} catch {
dispatch(fetchUserFailure());
}
};
return (
<div className="App">
<button onClick={handleClick}>User 정보 가져오기</button>
{loading ? (
<p>loading...</p>
) : name && email ? (
<>
<p>이름 : {name}</p>
<p>이메일 : {email}</p>
</>
) : null}
</div>
);
}
export default App;