이번에는 리액트 애플리케이션에서 애플리케이션에서 API 를 연동하는 방법을 배우게 될 것이다.
우리가 앱 애플리케이션을 만들면 데이터를 브라우저에서만 들고 있는게 아니라, 데이터를 보존시키고 다른 사람들도 조회 할 수 있게 하려면 서버를 만들고 서버의 API 를 사용해서 데이터를 읽고, 써야 한다.
실제 프로젝트에서는 주로 Redux 라는 라이브러리와 Redux 미들웨어를 함께 사용해서 구현을 하는 경우가 많다. 이 것에 대해서는 나중에 알아볼 것이고 이 도구들은 API 연동을 할 때 필수적인 요소는 아니다.
이번에 API 연동을 배우게 될 떄에는 Redux 없이, 그냥 컴포넌트에서 API 연동을 하게 될 때 어떻게 해야 하는지 알아보고 더 깔끔한 코드로 구현하는 방법도 다뤄보도록 하겠다.
API 연동을 위해서, 우선 새로운 프로젝트를 생성해준다.
$ npx create-react-app api-integrate
그리고, API 를 호출하기 위해서 axios 라는 라이브러리를 설치해준다.
$ cd api-integrate
$ npm install axios
우리는 axios 를 사용해서 GET, PUT, POST, DELETE 등의 메서드로 API 요청을 할 수 있다.
REST API 를 사용 할 때에는 하고 싶은 작업에 따라 다른 메서드로 요청을 할 수 있는데 메서드들은 다음 의미를 가지고 있다.
참고로 이 메소드 외에 PATCH, HEAD 와 같은 메서드들도 존재한다.
axios 의 사용법은...
import axios from 'axios';
axios.get('/users/1');
get 이 위치한 자리에는 메서드 이름을 소문자로 넣어준다. 그리고 새로운 데이터를 등록하고 싶으면 axios.post( ) 를 사용하면 된다.
그리고, 파라미터에는 API 의 주소를 넣어준다.
axios.post( ) 로 데이터를 등록 할 때에는 두번째 파라미터에 등록하고자 하는 정보를 넣을 수 있다.
axios.post('/users', {
username: 'blabla',
name: 'blabla'
});
이번에 API 연동 실습을 할 때에는 JSONPlaceholder 에 있는 연습용 API 를 사용해볼 것 이다.
사용 할 API 주소는 아래를 참고하면 된다.
https://jsonplaceholder.typicode.com/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"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
(...)
]
이번에는 useState 를 사용하여 요청 상태를 관리하고, useEffect 를 사용해서 컴포넌트가 렌더링되는 시점에 요청을 시작하는 작업을 해보겠다.
우리는 요청에 대한 상태를 관리할 때는 다음과 같이 3가지 상태를 관리해야 한다.
src 디렉토리에 Users.js 를 생성하고 다음과 같이 코드를 작성해준다.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function Users() {
const [users, setUsers] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
try {
// 요청이 시작 할 때에는 error 와 users 를 초기화하고
setError(null);
setUsers(null);
// loading 상태를 true 로 바꿉니다.
setLoading(true);
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
} catch (e) {
setError(e);
}
setLoading(false);
};
fetchUsers();
}, []);
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null;
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
);
}
export default Users;
참고로 useEffect 에서 첫 번째 파라미터로 등록해준 함수는 async 를 사용할 수 없기 때문에 함수 내부에서 async 를 사용하는 새로운 함수를 선언해준다.
로딩 상태가 활성화 됐을 땐 로딩중.. 이라는 문구를 보여주고,
그리고, users 에 값이 아직 없을 때에는 null 을 보여주도록 처리해두었다.
마지막에서는 users 배열을 렌더링하는 작업을 해주었다.
이제 이 컴포넌트가 잘 작동되는지 확인해보자!
App 컴포넌트에서 User 컴포넌트를 렌더링해보자
import React from 'react';
import Users from './Users';
function App() {
return <Users />;
}
export default App;
출처 : 벨로퍼트와 함께하는 모던 리액트
잘 작동된 것을 확인할 수 있다.
한 번 우리는 에러가 발생하는지도 확인해보자!
에러가 발생하는 것을 확인하기 위해 주소를 이상하게 바꿔보자...
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users/showmeerror'
);
에러가 발생했다고 문구가 나타난다.
이번에는 버튼을 눌러서 API 를 재요청하는 기능을 구현해보겠다.
그렇게 할려면, 아까 만들어준 fetchUsers 함수를 바깥으로 꺼내주고, 버튼을 만들어서 해당 함수를 연결해주면 된다.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function Users() {
const [users, setUsers] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchUsers = async () => {
try {
// 요청이 시작 할 때에는 error 와 users 를 초기화하고
setError(null);
setUsers(null);
// loading 상태를 true 로 바꿉니다.
setLoading(true);
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
} catch (e) {
setError(e);
}
setLoading(false);
};
useEffect(() => {
fetchUsers();
}, []);
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null;
return (
<>
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={fetchUsers}>다시 불러오기</button>
</>
);
}
export default Users;
결과는...
출처 : 벨로퍼트와 함께하는 모던 리액트
버튼을 클릭을하면 fetchUsers 함수가 재실행된다.
이번에는 전에 구현했던 User 컴포넌트에서 useState 대신에 useReducer 를 사용해서 구현을 해보도록 하겠다.
useReducer 를 사용하여 LOADING, SUCCESS, ERROR 를 액션에 따라 다르게 처리를 해보자!
import React, { useEffect, useReducer } from 'react';
import axios from 'axios';
function reducer(state, action) {
switch (action.type) {
case 'LOADING':
return {
loading: true,
data: null,
error: null
};
case 'SUCCESS':
return {
loading: false,
data: action.data,
error: null
};
case 'ERROR':
return {
loading: false,
data: null,
error: action.error
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
function Users() {
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: null
});
const fetchUsers = async () => {
dispatch({ type: 'LOADING' });
try {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'SUCCESS', data: response.data });
} catch (e) {
dispatch({ type: 'ERROR', error: e });
}
};
useEffect(() => {
fetchUsers();
}, []);
const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null;
return (
<>
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={fetchUsers}>다시 불러오기</button>
</>
);
}
export default Users;
useReducer 로 구현했을 때 장점은, useState 에서 setState 함수를 여러번 사용하지 않아도 된다는 점과 리듀서로 로직을 분리했으니 다른곳에서도 쉽게 재사용을 할 수 있다는 점 이다.
물론, 개인 취향에 맞게 useState 를 구현해도 상관없다.
다음에는 이번에 만든 reducer 를 기반으로, 커스텀 Hook 을 만들어보겠다.
참고 : 벨로퍼트와 함께하는 모던 리액트
느낀점 :