React는 UI를 작성하기 위한 자바스크립트 라이브러리입니다. React는 Redux와 자주 사용되곤 합니다. Redux는 전역 상태를 관리하기 위한 라이브러리입니다. 이 두 개의 라이브러리를 사용하여도 비동기적으로 API(Back-end)를 호출하는 것 또는 다른 사이드 이펙트를 관리하는 데 많은 어려움이 있습니다.
리덕스 미들웨어?: 리덕스 앱에서 '사이드 이펙트'를 처리하기 위해 사용하는 것
사이드 이펙트?: 우리가 작성한 코드가 외부 세계(Back-end, APIs)와 영향을 주고 받는 일
가장 대표적인 사이드 이펙트가 비동기적으로 API를 호출할 때입니다.
이 글에서는 이 사이드 이펙트를 어떻게 다루는 지에 대해 설명할 것입니다.
리덕스의 핵심 원칙: 스토어의 상태 변화를 기술하는 리듀서는 순수 함수로 작성되어야 한다
순수 함수?: Input이 같으면 Output도 항상 같아야 한다는 원칙
어떤 함수가 사이드 이펙트를 가지고 있습니다. 이는 외부와 데이터를 주고 받는 다는 뜻이고 결국 이 함수는 내부와 외부의 상태에 영향을 받게 됩니다. 이는 리덕스의 핵심 원칙에 위배됩니다.
이처럼 순수하지 못한 작업으로 인한 문제를 사전에 방지하고자 리덕스 미들웨어로 비동기 작업을 합니다.
Redux-thunk는 기본적인 리덕스 사이드 이펙트 로직(API 요청과 같은 간단한 비동기 로직)을 위한 미들웨어로 추천됩니다.
리듀서는 총 3가지의 상태를 가집니다.
1. 요청을 보냈을 때
2. 요청이 성공했을 때
3. 요청이 실패했을 때
여기 부터는 다른 곳의 예제를 인용하겠습니다.
우리에게 필요한 일은 5개 입니다.
npm install redux-thunk
import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './appReducers'; // Your Reducer
export function configureStore(initialState) {
const middleware = [thunk];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, initialState, composeEnhancers(applyMiddleware(...middleware)));
return store;
}
8~9 라인은 redux devtools을 설정하는 부분입니다. 나중에 문제가 생겼을 때 해결할 수 있도록 도움을 줄 말한 기능들을 제공합니다.
import Api from "../api"
export const LOAD_USERS_LOADING = 'REDUX_THUNK_LOAD_USERS_LOADING';
export const LOAD_USERS_SUCCESS = 'REDUX_THUNK_LOAD_USERS_SUCCESS';
export const LOAD_USERS_ERROR = 'REDUX_THUNK_LOAD_USERS_ERROR';
export const loadUsers = () => dispatch => {
dispatch({ type: LOAD_USERS_LOADING });
Api.getUsers()
.then(response => response.json())
.then(
data => dispatch({ type: LOAD_USERS_SUCCESS, data }),
error => dispatch({ type: LOAD_USERS_ERROR, error: error.message || 'Unexpected Error!!!' })
)
};
액션 생성자도 따로 분리해놓는 것을 추천합니다. 그러나 간단한 경우에는 망설임 없이 한 파일에 액션을 만드는 것도 상관없습니다.
import {LOAD_USERS_ERROR, LOAD_USERS_LOADING, LOAD_USERS_SUCCESS} from "./actions";
const initialState = {
data: [],
loading: false,
error: ''
};
export default function reduxThunkReducer(state = initialState, action) {
switch (action.type) {
case LOAD_USERS_LOADING: {
return {
...state,
loading: true,
error:''
};
}
case LOAD_USERS_SUCCESS: {
return {
...state,
data: action.data,
loading: false
}
}
case LOAD_USERS_ERROR: {
return {
...state,
loading: false,
error: action.error
};
}
default: {
return state;
}
}
}
import * as React from 'react';
import { connect } from 'react-redux';
import {loadUsers} from "./actions";
class UsersWithReduxThunk extends React.Component {
componentDidMount() {
this.props.loadUsers();
};
render() {
if (this.props.loading) {
return <div>Loading</div>
}
if (this.props.error) {
return <div style={{ color: 'red' }}>ERROR: {this.props.error}</div>
}
return (
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Active?</th>
<th>Posts</th>
<th>Messages</th>
</tr>
</thead>
<tbody>
{this.props.data.map(u =>
<tr key={u.id}>
<td>{u.firstName}</td>
<td>{u.lastName}</td>
<td>{u.active ? 'Yes' : 'No'}</td>
<td>{u.posts}</td>
<td>{u.messages}</td>
</tr>
)}
</tbody>
</table>
);
}
}
const mapStateToProps = state => ({
data: state.reduxThunk.data,
loading: state.reduxThunk.loading,
error: state.reduxThunk.error,
});
const mapDispatchToProps = {
loadUsers
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UsersWithReduxThunk);
간단한 예제를 만들려고 노력했지만, 끔찍해 보인다는 거 이해합니다. :)
로딩 표시
데이터
에러