1. 비동기 작업을 처리하는 미들웨어 사용
1.1.4 웹 요청 비동기 작업 처리하기
이번에는 thunk의 속성을 활용하기 웹 요청 비동기 작업을 처리하는 방법을 알아보겠습니다.
웹 요청을 욘습하기 위하여 JSONPlaceholder 에서 제공하는 가짜 API를 사용해 보겠습니다.
- 사용 API
#포스트 읽기(:id는 1~100사이 숫자)
GET htts://jsonplaceholder.typicode.com/posts/:id
#모든 사용자의 정보 불러오기
GET htts://jsonplaceholder.typicode.com/users
API를 호출할 때는 promise 기반 라이브러리인 axios를 사용합니다.
axios를 설치해 주세요.
yarn add axios
API를 모두 함수화해서 처리 하겠습니다.
각 API를 호출하는 함수를 따로 작성하면, 나중에 사용할 떄 가독성도 좋고 유지 보수도 쉬워집니다.
다른파일에서 불러와 사용할 수 있도록 export로 내보내기를 해주세요.
import axios from "axios";
export const getPost = id =>
axios.get(`htts://jsonplaceholder.typicode.com/posts/${id}`);
export const getUsers = (id) =>
axios.get(`htts://jsonplaceholder.typicode.com/users`);
이제 새로운 리듀서를 만들어 줄 차례입니다. 위 API를 사용하여 데이터를 받아와 상태를 관리할 sample 리듀서를 생성해 보겠습니다.
주석을 읽으면서 다음 코드를 작성해 보세요.
- modules/sample.js
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_POST_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를 통해 전달받은 데이터를 데이터의 형식이 어떤 구조인지 파악해야 합니다.
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 && 를 사용했기 때문에 객체가 유효한 값일 경우만 title, body 값을 보여줍니다.
데이터가 없는 상태에서 title 값을 조회하려고 하면 javascript 오류가 발생하니 유효성 검사를 체크해줄 필요가 있습니다.
- containers/SampleContainers.js
import React from "react"
import { connect } from "react-redux"
import Sample from "../components/Sample"
import { getPost, getUsers } from "../lib/api"
const [ useEffect ] = React
const SampleContainer = ({
getPost,
getUsers,
post,
users,
loadingPost,
loadingUsers
}) => {
// 클래스 형태 컴포넌트이면 componentDidMount 사용
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.js에서 CounterContainer를 제외하고 SampleContainer 컴포넌트를 렌더링 해보세요.
- App.js
import React from 'react';
import SampleContainer from './containers/SampleContainer';
function App() {
return (
<div>
<SampleContainer/>
</div>
);
}
export default App;
이제 브라우저에서 실행하여 이상이 없는지를 확인해 보세요.
아래와 같이 API가 화면상에 노출되면 성공입니다.
'프론트엔드 > React' 카테고리의 다른 글
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[7] (0) | 2022.05.01 |
---|---|
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[6] (0) | 2022.04.27 |
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[4] (0) | 2022.04.19 |
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[3] (0) | 2022.04.19 |
[프론트엔드] React 동작 구조 (가상 DOM) (0) | 2022.04.18 |