벨로퍼트 리액트 - 4. API 연동하기
4-1. API 연동의 기본
axios : API 호출을 위해 사용하는 라이브러리. GET, PUT, POST, DELETE 등의 메서드로 API 요청을 할 수 있다.
** REST API : 웹의 장점을 최대한 활용할 수 있는 아키텍처.
자원(Resource, URI) / 행위(Verb, HTTP Method), 표현(Respresentations)로 구성.
URI는 정보의 자원을 표현해야 하며 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현.
- GET : 데이터 조회
- POST : 데이터 등록
- PUT : 데이터 수정
- DELETE : 데이터 제거
axios의 기본 사용법
import axios from 'axios';
axios.get('/users/1');
get 이 위치한 자리에는 메서드 이름을 소문자로 넣어줌.
파라미터에는 API의 주소를 넣음.
post : 두번째 파라미터에 등록하고자 하는 정보를 추가
axios.post('/users', {
username: 'aaa',
name: 'bbb'
});
연습용 API 주소 : https://jsonplaceholder.typicode.com/
사용할주소
https://jsonplaceholder.typicode.com/users
useState(상태관리)와 useEffect(요청 시점 설정)로 데이터 로딩하기
useState = 요청 상태 관리
useEffect = 컴포넌트가 렌더링 될때 요청시작하기 설정.
요청에 대한 상태를 관리할때 관리해주어야 하는것
- 요청의 결과
- 로딩 상태
- 에러
예제코드
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();
}, []); // 지켜볼 deps가 없으므로 최초 1번만 실행
if (loading) return <div>로딩중...</div>; // 로딩상태 활성화
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null; // users 값이 아직 없을때
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
);
}
export default Users;
다시 불러오기 버튼 생성
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();
}, []); // 지켜볼 deps가 없으므로 최초 1번만 실행
if (loading) return <div>로딩중...</div>; // 로딩상태 활성화
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null; // users 값이 아직 없을때
return (
<>
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={fetchUsers}>다시 불러오기</button>
</>
);
}
export default Users;
useReducer로 요청 상태 관리하기
useState 대신 useReducer 사용하기
LOADING, SUCCESS, ERROR 로 액션을 분리
import React, {useReducer, useEffect} from 'react';
import axios from 'axios';
function reducer(state, action) {
switch (action.type) {
case 'LOADING' :
return {
loading: true, // 로딩시 loading true로 변경
data: null,
error: null
};
case 'SUCCESS' :
return {
loading: false,
data: action.data, // 요청완료(성공)시 data 출력. action.data = state.data = users
error: null
}
case 'ERROR' :
return {
loading: false,
data: null,
error: action.error // 에러시 에러 내용 출력 action.error 는 e
}
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();
}, []); // 지켜볼 deps가 없으므로 최초 1번만 실행
const { loading, data: users, error} = state; // state.data를 users 키워드로 조회
if (loading) return <div>로딩중...</div>; // 로딩상태 활성화
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null; // users 값이 아직 없을때
return (
<>
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={fetchUsers}>다시 불러오기</button>
</>
);
}
export default Users;
useAsync 커스텀 Hook으로 만들어서 사용하기
데이터를 요청할때마다 리듀서를 작성하는것은 번거롭기 때문에 커스텀 Hook으로 만들어서 재사용하기.
useAsync.js 생성
import { useReducer, useEffect } from "react";
function reducer(state, action) {
switch (action.type) {
case 'LOADING' :
return {
loading: true, // 로딩시 loading true로 변경
data: null,
error: null
};
case 'SUCCESS' :
return {
loading: false,
data: action.data, // 요청완료(성공)시 data 출력. action.data = state.data = users
error: null
}
case 'ERROR' :
return {
loading: false,
data: null,
error: action.error // 에러시 에러 내용 출력 action.error 는 e
}
default :
throw new Error(`Unhandled action type: ${action.type}`);
}
};
function useAsync(callback, deps = []) {
// callback(API 요청을 시작하는 함수), deps(해당 함수 안에서 사용할 useEffect의 deps).
// deps는 파라미터가 필요하고, 바뀔때 새로운 데이터를 불러오고 싶은 경우에 활용할 수 있음.
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: false
});
const fetchData = async () => {
dispatch({type: 'LOADING'});
try {
const data = await callback();
dispatch({type: 'SUCCESS', data});
} catch (e) {
dispatch({type: 'ERROR', error: e});
}
}
useEffect(() => {
fetchData();
// eslint 설정을 다음 줄에서만 비활성화
// eslint-disable-next-line
}, deps);
return [state, fetchData]; // 요청 관련 상태와 fetchData 함수 반환.
}
export default useAsync;
= > Hook 사용하기
import React, {useReducer, useEffect} from 'react';
import axios from 'axios';
import useAsync from './useAsync';
// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
)
return response.data;
}
function Users() {
const [state, refetch] = useAsync(getUsers, []);
const { loading, data: users, error} = state; // state.data를 users 키워드로 조회
if (loading) return <div>로딩중...</div>; // 로딩상태 활성화
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null; // users 값이 아직 없을때
return (
<>
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
</>
);
}
export default Users;
데이터 나중에 불러오기
특정 버튼을 눌렀을때만 API 요청을 하고싶다면?
useAsync.js
import { useReducer, useEffect } from "react";
function reducer(state, action) {
switch (action.type) {
case 'LOADING' :
return {
loading: true, // 로딩시 loading true로 변경
data: null,
error: null
};
case 'SUCCESS' :
return {
loading: false,
data: action.data, // 요청완료(성공)시 data 출력. action.data = state.data = users
error: null
}
case 'ERROR' :
return {
loading: false,
data: null,
error: action.error // 에러시 에러 내용 출력 action.error 는 e
}
default :
throw new Error(`Unhandled action type: ${action.type}`);
}
};
function useAsync(callback, deps = [], skip = false) {
// callback(API 요청을 시작하는 함수), deps(해당 함수 안에서 사용할 useEffect의 deps).
// deps는 파라미터가 필요하고, 바뀔때 새로운 데이터를 불러오고 싶은 경우에 활용할 수 있음.
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: false
});
const fetchData = async () => {
dispatch({type: 'LOADING'});
try {
const data = await callback();
dispatch({type: 'SUCCESS', data});
} catch (e) {
dispatch({type: 'ERROR', error: e});
}
}
useEffect(() => {
if (skip) return; // skip이 true 라면 아무 작업도 하지 않고 return 하도록 함
fetchData();
// eslint 설정을 다음 줄에서만 비활성화
// eslint-disable-next-line
}, deps);
return [state, fetchData]; // 요청 관련 상태와 fetchData 함수 반환.
}
export default useAsync;
Users.js
import React, {useReducer, useEffect} from 'react';
import axios from 'axios';
import useAsync from './useAsync';
// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
)
return response.data;
}
function Users() {
const [state, refetch] = useAsync(getUsers, [], true); // skip이 true
const { loading, data: users, error} = state; // state.data를 users 키워드로 조회
if (loading) return <div>로딩중...</div>; // 로딩상태 활성화
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return <button onClick={refetch}>불러오기</button>; // users 값이 아직 없을때
return (
<>
<ul>
{users.map(user => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
</>
);
}
export default Users;
다시 불러오기
);
}
export default Users;
API에 파라미터가 필요한 경우
User 컴포넌트 생성 / id 값을 props로 받아와서 맨 뒤에 id 넣어서 API 요청하기
ex ) https://jsonplaceholder.typicode.com/users/1
User.js
import React from "react";
import axios from "axios";
import useAsync from "./useAsync";
async function getUser(id) {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${id}`
);
return response.data;
}
function User({id}) {
const [state] = useAsync(() => getUser(id), [id]); // useAsync 사용시 파라미터 포함시켜서 호출(id)
const {loading, data: user, error} = state;
if (loading) return <div>로딩중...</div>;
if (error) return <div>에러가 발생했습니다</div>
if (!user) return null;
return (
<div>
<h2>{user.username}</h2>
<p>
<b>Email: </b> {user.email}
</p>
</div>
);
}
export default User
Users.js ⇒ useState로 userId 상태 관리. 초기값은 null, 리스트 항목 클릭시 해당 id를 userId 값으로 설정함
import React, {useReducer, useEffect, useState} from 'react';
import axios from 'axios';
import useAsync from './useAsync';
import User from './User';
// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
)
return response.data;
}
function Users() {
const [userId, setUserId] = useState(null); // 클릭된 id 상태 관리
const [state, refetch] = useAsync(getUsers, [], true); // skip이 true
const { loading, data: users, error} = state; // state.data를 users 키워드로 조회
if (loading) return <div>로딩중...</div>; // 로딩상태 활성화
if (error) return <div>에러가 발생했습니다</div>;
// if (!users) return null; // users 값이 아직 없을때
if (!users) return <button onClick={refetch}>불러오기</button>; // users 값이 아직 없을때
return (
<>
<ul>
{users.map(user => (
<li
key={user.id}
onClick={() => setUserId(user.id)} // 클릭시 이 id로 userId 상태 변경
style={{cursor:'pointer'}}
>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
{userId && <User id={userId}/>}
</>
);
}
export default Users;