일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 예외처리
- 메소드오버로딩
- 다형성
- 객체 비교
- 정수형타입
- 인터페이스
- 예외미루기
- 한국건설관리시스템
- 컬렉션프레임워크
- 추상메서드
- cursor문
- EnhancedFor
- abstract
- 참조형변수
- 대덕인재개발원
- 자동차수리시스템
- 어윈 사용법
- 제네릭
- 집합_SET
- exception
- 사용자예외클래스생성
- 컬렉션 타입
- 생성자오버로드
- 자바
- 환경설정
- NestedFor
- oracle
- GRANT VIEW
- Java
- 오라클
- Today
- Total
거니의 velog
(12) 리덕스 미들웨어를 통해 비동기 작업 관리 2 본문
3. 비동기 작업을 처리하는 미들웨어 사용
* 미들웨어가 어떤 방식으로 작동하는지 이해했는가? 이제 오픈 소스 커뮤니티에 공개된 미들웨어를 사용하여 리덕스를 사용하고 있는 프로젝트에서 비동기 작업을 더욱 효율적으로 관리해 보겠다.
* 비동기 작업을 처리할 때 도움을 주는 미들웨어는 정말 다양하다. 이 책에서 다룰 미들웨어는 다음과 같다.
- redux-thunk : 비동기 작업을 처리할 때 가장 많이 사용하는 미들웨어이다. 객체가 아닌 함수 형태의 액션을
디스패치할 수 있게 해 준다.
- redux-saga : redux-thunk 다음으로 많이 사용하는 비동기 작업 관련 미들웨어 라이브러리이다. 특정 액션이
디스패치되었을 때 정해진 로직에 따라 다른 액션을 디스패치시키는 규칙을 작성하여 비동기 작업을 처리할 수
있게 해 준다.
(1) redux-thunk
* redux-thunk는 리덕스를 사용하는 프로젝트에서 비동기 작업을 처리할 때 가장 기본적으로 사용하는 미들웨어이다. 리덕스의 창시자인 댄 아브라모프(Dan Abramov)가 만들었으며, 리덕스 공식 매뉴얼에서도 이 미들웨어를 사용하여 비동기 작업을 다루는 예시를 보여 준다.
[1] Thunk란?
* Thunk는 특정 작업을 나중에 할 수 있도록 미루기 위해 함수 형태로 감싼 것을 의미한다. 예를 들어 주어진 파라미터에 1을 더하는 함수를 만들고 싶다면 다음과 같이 작성할 것이다.
const addOne = x => x + 1;
addOne(1); // 2
* 이 코드를 실행하면 addOne을 호출했을 때 바로 1 + 1이 연산된다. 그런데 이 연산 작업을 나중에 하도록 미루고 싶다면 어떻게 해야 할까?
const addOne = x => x + 1;
function addOneThunk(x) {
const thunk = () => addOne(x);
return thunk;
}
const fn = addOneThunk(1);
setTimeout(() => {
const value = fn(); // fn이 실행되는 시점에서 연산
console.log(value);
}, 1000);
* 이렇게 하면 특정 작업을 나중에 하도록 미룰 수 있다.
* 만약 addOneThunk를 화살표 함수로만 사용한다면 다음과 같이 구현할 수 있다.
const addOne = x => x + 1;
const addOneThunk = x => () => addOne(x);
const fn = addOneThunk(1);
setTimeout(() => {
const value = fn(); // fn이 실행되는 시점에서 연산
console.log(value);
}, 1000);
* redux-thunk 라이브러리를 사용하면 thunk 함수를 만들어서 디스패치할 수 있다. 그러면 리덕스 미들웨어가 그 함수를 전달받아 store의 dispatch와 getState를 파라미터로 넣어서 호출해 준다.
* 다음은 redux-thunk에서 사용할 수 있는 예시 thunk 함수이다.
const sampleThunk = () => (dispatch, getState) => {
// 현재 상태를 참조할 수 있고,
// 새 액션을 디스패치할 수도 있다.
}
[2] 미들웨어 적용하기
* redux-thunk 미들웨어를 설치하고 프로젝트에 적용해 보자.
* 다음 명령어로 라이브러리를 설치하자.
$ yarn add redux-thunk
* 스토어를 만들 때 redux-thunk를 적용하자.
[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 rootReducer from "./modules";
import { Provider } from "react-redux";
// import loggerMiddleware from "./lib/loggerMiddleware";
import { createLogger } from "redux-logger";
import { thunk as ReduxThunk } from "redux-thunk"; // Import the 'thunk' function
const logger = createLogger();
const store = createStore(rootReducer, applyMiddleware(logger, ReduxThunk));
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
reportWebVitals();
[3] thunk 생성 함수 만들기
* redux-thunk는 액션 생성 함수에서 일반 액션 객체를 반환하는 대신에 함수를 반환한다. increaseAsync와 decreaseAsync 함수를 만들어 카운터 값을 비동기적으로 한번 변경시켜 보자.
[modules/counter.js]
import React from "react";
import { createAction, handleActions } from "redux-actions";
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
// 1초 뒤에 increase 혹은 decrease 함수를 디스패치함
export const increaseAsync = () => (dispatch) => {
setTimeout(() => {
dispatch(increase());
}, 1000);
};
export const decreaseAsync = () => (dispatch) => {
setTimeout(() => {
dispatch(decrease());
}, 1000);
};
const initialState = 0; // 상태는 꼭 객체일 필요는 없다. 숫자도 잘 작동한다.
const counter = handleActions(
{
[INCREASE]: (state) => state + 1,
[DECREASE]: (state) => state - 1,
},
initialState
);
export default counter;
* 리덕스 모듈을 수정했으면 CounterContainer에서 호출하던 액션 생성 함수도 변경해 주자.
[containers/CounterContainer.js]
import React from "react";
import { connect } from "react-redux";
import { increaseAsync, decreaseAsync } from "../modules/counter";
import Counter from "../components/Counter";
const CounterContainer = ({ number, increaseAsync, decreaseAsync }) => {
return (
<Counter
number={number}
onIncrease={increaseAsync}
onDecrease={decreaseAsync}
/>
);
};
export default connect(
(state) => ({
number: state.counter,
}),
{
increaseAsync,
decreaseAsync,
}
)(CounterContainer);
* 코드를 저장하고 브라우저에서 버튼을 눌러 보자. 숫자가 1초 뒤에 변경되는가? 개발자 도구를 열어서 발생한 액션 기록을 확인해 보자.
* 처음 디스패치되는 액션은 함수 형태이고, 두 번째 액션은 객체 형태이다.
[4] 웹 요청 비동기 작업 처리하기
* 이번에는 thunk의 속성을 활용하여 웹 요청 비동기 작업을 처리하는 방법에 대해 알아보자. 웹 요청을 연습하기 위해 JSONPlaceholder에서 제공되는 가짜 API를 사용하겠다.
https://jsonplaceholder.typicode.com/
* 사용할 API는 다음과 같다.
# 포스트 읽기(:id는 1~100 사이의 숫자)
GET https://jsonplaceholder.typicode.com/posts/:id
# 모든 사용자 정보 불러오기
GET https://jsonplaceholder.typicode.com/users
* API를 호출할 때는 주로 Promise 기반 웹 클라이언트인 axios를 사용한다. 해당 라이브러리를 설치해 주자.
$ yarn add axios
* API를 모두 함수화해 주자. 각 API를 호출하는 함수를 따로 작성하면, 나중에 사용할 때 가독성도 좋고 유지 보수도 쉬워진다. 다른 파일에서 불러와 사용할 수 있도록 export를 사용하여 내보내 주자.
[lib/api.js]
import axios from "axios";
export const getPost = (id) =>
axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);
export const getUsers = (id) =>
axios.get(`https://jsonplaceholder.typicode.com/users`);
* 이제 새로운 리듀서를 만들어 줄 차례이다. 위 API를 사용하여 데이터를 받아와 상태를 관리할 sample이라는 리듀서를 생성해 보겠다. 주석을 읽으면서 다음 코드를 작성해 보자.
import { handleActions } from "redux-actions";
import * as api from "../lib/api";
// 액션 타입을 선언한다.
// 한 요청당 세 개를 만들어야 한다.
const GET_POST = "sample/GET_POST";
const GET_POST_SUCCESS = "sample/GET_POST_SUCCESS";
const GET_POST_FAILURE = "sample/GET_POST_FAILURE";
const GET_USERS = "sample/GET_USERS";
const GET_USERS_SUCCESS = "sample/GET_USERS_SUCCESS";
const GET_USERS_FAILURE = "sample/GET_USERS_FAILURE";
// thunk 함수를 생성한다.
// thunk 함수 내부에서는 시작할 때, 성공했을 때, 실패했을 때 다른 액션을 디스패치한다.
export const getPost = (id) => async (dispatch) => {
dispatch({ type: GET_POST }); // 요청을 시작한 것을 알림
try {
const response = await api.getPost(id);
dispatch({
type: GET_POST_SUCCESS,
payload: response.data,
}); // 요청 성공
} catch (e) {
dispatch({
type: GET_POST_FAILURE,
payload: e,
error: true,
}); // 에러 발생
throw e; // 나중에 컴포넌트단에서 에러를 조회할 수 있게 해 줌
}
};
export const getUsers = () => async (dispatch) => {
dispatch({ type: GET_USERS }); // 요청을 시작한 것을 알림
try {
const response = await api.getUsers();
dispatch({
type: GET_USERS_SUCCESS,
payload: response.data,
}); // 요청 성공
} catch (e) {
dispatch({
type: GET_USERS_FAILURE,
payload: e,
error: true,
}); // 에러 발생
throw e; // 나중에 컴포넌트단에서 에러를 조회할 수 있게 해 줌
}
};
// 초기 상태를 선언한다.
// 요청의 로딩 중 상태는 loading이라는 객체에서 관리한다.
const initialState = {
loading: {
GET_POST: false,
GET_USERS: false,
},
post: null,
users: null,
};
const sample = handleActions(
{
[GET_POST]: (state) => ({
...state,
loading: {
...state.loading,
GET_POST: true, // 요청 시작
},
}),
[GET_POST_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_POST: false, // 요청 완료
},
post: action.payload,
}),
[GET_POST_FAILURE]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_POST: false, // 요청 완료
},
}),
[GET_USERS]: (state) => ({
...state,
loading: {
...state.loading,
GET_USERS: true, // 요청 시작
},
}),
[GET_USERS_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_USERS: false, // 요청 완료
},
users: action.payload,
}),
[GET_USERS_FAILURE]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_USERS: false, // 요청 완료
},
}),
},
initialState
);
export default sample;
* 코드에서 반복되는 로직이 꽤 있다. 우선 컨테이너 컴포넌트를 사용하여 데이터 요청을 성공적으로 처리하고, 나중에 반복되는 로직을 따로 분리하여 재사용하는 형태로 코드를 리팩토링하겠다.
* 리듀서를 다 작성했다면 해당 리듀서를 루트 리듀서에 포함시키자.
[modules/index.js]
import { combineReducers } from "redux";
import counter from "./counter";
import sample from "./sample";
const rootReducer = combineReducers({ counter, sample });
export default rootReducer;
* 우선 데이터를 렌더링할 프레젠테이셔널 컴포넌트부터 작성한다. 이 컴포넌트를 작성하려면 먼저 API를 통해 전달받은 데이터의 형식이 어떤 구조인지 확인해야 한다.
// post
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
// users
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
(......)
]
* 이번에 만들 컴포넌트는 post의 경우 title과 body만 보여주고, user의 경우 username과 email만 보여 줄 것이다.
* Sample 컴포넌트를 다음과 같이 작성해 보자.
[components/Sample.js]
import React from "react";
const Sample = ({ loadingPost, loadingUsers, post, users }) => {
return (
<div>
<section>
<h1>포스트</h1>
{loadingPost && "로딩 중..."}
{!loadingPost && post && (
<div>
<h3>{post.title}</h3>
<h3>{post.body}</h3>
</div>
)}
</section>
<hr />
<section>
<h1>사용자 목록</h1>
{loadingUsers && "로딩 중..."}
{!loadingUsers && users && (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.username} ({user.email})
</li>
))}
</ul>
)}
</section>
</div>
);
};
export default Sample;
* 데이터를 불러와서 렌더링해 줄 때는 유효성 검사를 해 주는 것이 중요하다. 예를 들어 post && 를 사용하면 post 객체가 유효할 때만 그 내부의 post.title 혹은 post.body 값을 보여 준다. 만약 데이터가 없는 상태라면 post.title을 조회하려고 할 때 자바스크립트 오류가 발생하니 반드시 유효성 검사를 해주어야 한다.
* users 도 마찬가지로 데이터가 배열 형태로 들어올 것을 기대하고 map 함수를 사용하고 있다. 하지만 유효성 검사를 하지 않으면 null 값에 대해 map 함수를 호출하고, 결국 map 함수가 존재하지 않아 오류가 발생한다.
* 이제 컨테이너 컴포넌트를 만들어 보자.
[containers/SampleContainer.js]
import React from "react";
import { connect } from "react-redux";
import Sample from "../components/Sample";
import { getPost, getUsers } from "../modules/sample"; // 액션 생성 함수를 가져와야 함
const { useEffect } = React;
const SampleContainer = ({
getPost,
getUsers,
post,
users,
loadingPost,
loadingUsers,
}) => {
useEffect(() => {
getPost(1); // 액션 생성 함수를 호출해야 함
getUsers(1); // 액션 생성 함수를 호출해야 함
}, [getPost, getUsers]);
return (
<Sample
post={post}
users={users}
loadingPost={loadingPost}
loadingUsers={loadingUsers}
/>
);
};
export default connect(
({ sample }) => ({
post: sample.post,
users: sample.users,
loadingPost: sample.loading.GET_POST,
loadingUsers: sample.loading.GET_USERS,
}),
{ getPost, getUsers }
)(SampleContainer);
* 그 다음 App 컴포넌트에서 CouinterContainer 대신 SampleContainer를 렌더링해 보자.
[App.js]
import React from "react";
import SampleContainer from "./containers/SampleContainer";
const App = () => {
return (
<div>
<SampleContainer />
</div>
);
};
export default App;
* 브라우저를 열어 데이터가 잘 로딩되었는지, 개발자 도구 콘솔에서 액션이 어떤 순서로 발생하는지 확인해 보자.
[5] 리팩토링
* API를 요청해야 할 때마다 17줄 정도 되는 thunk 함수를 작성하는 것이 아니라 로딩 상태를 리듀서에서 관리하는 작업은 귀찮을 뿐 아니라 코드도 길어지게 만든다. 그러므로 반복되는 로직을 따로 분리하여 코드의 양을 줄여 보자.
export default function createRequestThunk(type, request) {
// 성공 및 실패 액션 타입을 정의한다.
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return (params) => async (dispatch) => {
dispatch({ type }); // 시작됨
try {
const response = await request(params);
dispatch({
type: SUCCESS,
payload: response.data,
}); // 성공
} catch (e) {
dispatch({
type: FAILURE,
payload: e,
error: true,
}); // 에러 발생
throw e;
}
};
}
// 사용법 : createRequestThunk('GET_USERS', api.getUsers)
* 이반에 만든 유틸 함수는 API 요청을 해 주는 thunk 함수는 한 줄로 생성할 수 있게 해 준다. 액션 타입과 API를 요청하는 함수를 파라미터로 넣어 주면 나머지 작업을 대신 처리해 준다. 이 함수를 사용하여 기존 thunk 함수의 코드를 대체시켜 보자.
[modules/sample.js]
import { handleActions } from "redux-actions";
import * as api from "../lib/api";
import createRequestThunk from "../lib/createRequestThunk";
// 액션 타입을 선언한다.
// 한 요청당 세 개를 만들어야 한다.
const GET_POST = "sample/GET_POST";
const GET_POST_SUCCESS = "sample/GET_POST_SUCCESS";
const GET_POST_FAILURE = "sample/GET_POST_FAILURE";
const GET_USERS = "sample/GET_USERS";
const GET_USERS_SUCCESS = "sample/GET_USERS_SUCCESS";
const GET_USERS_FAILURE = "sample/GET_USERS_FAILURE";
// thunk 함수를 생성한다.
// thunk 함수 내부에서는 시작할 때, 성공했을 때, 실패했을 때 다른 액션을 디스패치한다.
export const getPost = createRequestThunk(GET_POST, api.getPost);
export const getUsers = createRequestThunk(GET_USERS, api.getUsers);
// 초기 상태를 선언한다.
// 요청의 로딩 중 상태는 loading이라는 객체에서 관리한다.
const initialState = {
loading: {
GET_POST: false,
GET_USERS: false,
},
post: null,
users: null,
};
const sample = handleActions(
{
[GET_POST]: (state) => ({
...state,
loading: {
...state.loading,
GET_POST: true, // 요청 시작
},
}),
[GET_POST_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_POST: false, // 요청 완료
},
post: action.payload,
}),
[GET_POST_FAILURE]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_POST: false, // 요청 완료
},
}),
[GET_USERS]: (state) => ({
...state,
loading: {
...state.loading,
GET_USERS: true, // 요청 시작
},
}),
[GET_USERS_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_USERS: false, // 요청 완료
},
users: action.payload,
}),
[GET_USERS_FAILURE]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_USERS: false, // 요청 완료
},
}),
},
initialState
);
export default sample;
* 어떤가? 코드가 많이 줄었다. 브라우저를 열어서 이전과 똑같이 작동하는지도 확인해 보자. 똑같은 기능을 훨씬 짧은 코드로 구현했다!
* 이번에는 요청의 로딩 상태를 관리하는 작업을 개선하겠다. 기존에는 리듀서 내부에서 각 요청에 관련된 액션이 디스패치될 때마다 로딩 상태를 변경해 주었는데, 이 작업을 로딩 상태만 관리하는 리덕스 모듈을 따로 생성하여 처리하겠다.
[modules/loading.js]
import { createAction, handleActions } from "redux-actions";
const START_LOADING = "loading/START_LOADING";
const FINISH_LOADING = "loading/FINISH_LOADING";
/*
요청을 위한 액션 타입을 payload로 설정한다(예: "sample/GET_POST").
*/
export const startLoading = createAction(
START_LOADING,
(requestType) => requestType
);
export const finishLoading = createAction(
FINISH_LOADING,
(requestType) => requestType
);
const initialState = {};
const loading = handleActions(
{
[START_LOADING]: (state, action) => ({
...state,
[action.payload]: true,
}),
[FINISH_LOADING]: (state, action) => ({
...state,
[action.payload]: false,
}),
},
initialState
);
export default loading;
* 다음은 요청이 시작될 때 디스패치할 액션이다.
{
type: 'loading/START_LOADING',
payload: 'sample/GET_POST'
}
* 위 액션이 디스패치되면 loading 리듀서가 관리하고 있는 상태에서 sample/GET_POST 값을 true로 설정해 준다. 만약 기존 상태에 sample/GET_POST 필드가 존재하지 않으면 새로 값을 설정해 준다.
* 그리고 요청이 끝나면 다음 액션을 디스패치해야 한다.
{
type: 'loading/FINISH_LOADING',
payload: 'sample/GET_POST'
}
* 그러면 기존에 true로 설정했던 값을 다시 false로 전환해 준다.
* 리듀서를 다 작성했으면 루트 리듀서에 포함시키자.
[modules/index.js]
import { combineReducers } from "redux";
import counter from "./counter";
import sample from "./sample";
import loading from "./loading";
const rootReducer = combineReducers({ counter, sample, loading });
export default rootReducer;
* loading 리덕스 모듈에서 만든 액션 생성 함수는 앞에서 만든 createRequestThunk에서 사용해 준다.
[lib/createRequestThunk.js]
import { finishLoading, startLoading } from "../modules/loading";
export default function createRequestThunk(type, request) {
// 성공 및 실패 액션 타입을 정의한다.
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return (params) => async (dispatch) => {
dispatch({ type }); // 시작됨
dispatch(startLoading(type));
try {
const response = await request(params);
dispatch({
type: SUCCESS,
payload: response.data,
}); // 성공
dispatch(finishLoading(type));
} catch (e) {
dispatch({
type: FAILURE,
payload: e,
error: true,
}); // 에러 발생
dispatch(startLoading(type));
throw e;
}
};
}
// 사용법 : createRequestThunk('GET_USERS', api.getUsers)
* 그러면 SampleContainer에서 로딩 상태를 다음과 같이 조회할 수 있다.
[containers/SampleContainer.js]
import React from "react";
import { connect } from "react-redux";
import Sample from "../components/Sample";
import { getPost, getUsers } from "../modules/sample"; // 액션 생성 함수를 가져와야 함
import loading from "../modules/loading";
const { useEffect } = React;
const SampleContainer = ({
getPost,
getUsers,
post,
users,
loadingPost,
loadingUsers,
}) => {
useEffect(() => {
getPost(1); // 액션 생성 함수를 호출해야 함
getUsers(1); // 액션 생성 함수를 호출해야 함
}, [getPost, getUsers]);
return (
<Sample
post={post}
users={users}
loadingPost={loadingPost}
loadingUsers={loadingUsers}
/>
);
};
export default connect(
({ sample }) => ({
post: sample.post,
users: sample.users,
loadingPost: loading["sample/GET_POST"],
loadingUsers: loading["sample/GET_USERS"],
}),
{ getPost, getUsers }
)(SampleContainer);
* 코드를 저장하고 브라우저를 열어서 기능이 제대로 작동하는지 확인해 보자.
* 이제 sample 리듀서에서 불필요한 코드를 지워 보자.
[modules/sample.js]
import { handleActions } from "redux-actions";
import * as api from "../lib/api";
import createRequestThunk from "../lib/createRequestThunk";
// 액션 타입을 선언한다.
// 한 요청당 세 개를 만들어야 한다.
const GET_POST = "sample/GET_POST";
const GET_POST_SUCCESS = "sample/GET_POST_SUCCESS";
const GET_USERS = "sample/GET_USERS";
const GET_USERS_SUCCESS = "sample/GET_USERS_SUCCESS";
// thunk 함수를 생성한다.
// thunk 함수 내부에서는 시작할 때, 성공했을 때, 실패했을 때 다른 액션을 디스패치한다.
export const getPost = createRequestThunk(GET_POST, api.getPost);
export const getUsers = createRequestThunk(GET_USERS, api.getUsers);
// 초기 상태를 선언한다.
// 요청의 로딩 중 상태는 loading이라는 객체에서 관리한다.
const initialState = {
post: null,
users: null,
};
const sample = handleActions(
{
[GET_POST_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_POST: false, // 요청 완료
},
post: action.payload,
}),
[GET_USERS_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_USERS: false, // 요청 완료
},
users: action.payload,
}),
},
initialState
);
export default sample;
* 코드가 훨씬 깔끔해졌다! 이제 sample 리듀서에서는 로딩 중에 대한 상태를 관리할 필요가 없다. 성공했을 때의 케이스만 잘 관리해 주면 된다. 추가로 실패했을 때의 케이스를 관리하고 싶다면 _FAILURE가 붙은 액션을 리듀서에서 처리해 주면 된다. 혹은 컨테이너 컴포넌트에서 try/catch 구문을 사용하여 에러 값을 조회할 수도 있다.
[SampleContainer.js - useEffect]
useEffect(() => {
// useEffect에 파라미터로 넣는 함수는 async로 할 수 없기 때문에
// 그 내부에서 async 함수를 선언하고 호출해 준다.
const fn = async () => {
try {
await getPost(1);
await getUsers(1);
} catch (e) {
console.log(e); // 에러 조회
}
};
fn();
}, [getPost, getUsers]);
* redux-thunk 를 처음 쓸 때는 비록 작성해야 할 코드가 많아서 불편할 수 있지만, 유용한 함수와 리듀서를 만들어서 상태를 관리한다면 매우 깔끔한 코드로 기능을 구현할 수 있다.
'React > React_리액트 심화' 카테고리의 다른 글
(14) 코드 스플리팅 (0) | 2023.12.14 |
---|---|
(13) 리덕스 미들웨어를 통해 비동기 작업 관리 3 (0) | 2023.12.14 |
(11) 리덕스 미들웨어를 통해 비동기 작업 관리 1 (0) | 2023.12.13 |
(10) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 5 (0) | 2023.12.13 |
(9) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 4 (0) | 2023.12.13 |