React
는 Front-end
에서 사용
그 말은 즉, 데이터를 모두가 사용할 수 있게 저장할 수 없다는 것을 의미하고, 또 그것은 다른 사용자와의 데이터 공유가 이뤄질 수 없음을 의미
따라서 API Server
를 구축해 쓰거나, 남이 만든 것을 가져와서 쓰면, 값을 조작할 경우 API Server
가 갱신이 되면서, 같은 API Server
를 공유하는 유저들 또한 바뀐 값을 사용할 수 있게 됨
yarn add axios
axios
는 REST API
에 데이터를 요청할 때, 이를 Promise
로 처리할 수 있게 해주는 라이브러리
HTTP method
를 통해 서버와 클라이언트의 통신을 할 경우에 사용해서 상대가 어떤 통신을 요청하는지, 그리고 내가 어떤 통신을 요청해야 하는지 정할 수 있음
axios.get('/users/1');
이 경우는 users
들 중에 1번째 user
에 대한 정보를 불러와달라는 것을 의미, 물론 1번째가 아니라 id
가 1인 유저가 될 수도 있고 그건 서버가 정해놓는 규칙이라 상황에 따라 다름 (axios.get('/articles/1')
이면 1번 글을 불러오라는 의미 혹은, 1번 유저가 작성한 게시글을 모두 불러오라는 의미 등등 여러 개로 쓰일 수 있음)
axios.post('/users', {
username: 'aaaaa',
name: 'JohnQue',
password: '1234567',
email: 'donot@have.th.at',
});
이런식으로 작성되면, 이번에는 users
에 해당 user
객체를 신규 등록해달라는 의미일 수 있고,
axios.post('/articles', {
username: 'aaaaa',
contents: 'react is good~!',
});
이런식으로 작성되면, articles
에 해당 article
을 등록해달라는 의미로 사용될 수 있음
즉 서버가 어떤 규칙을 정해놓으면 클라이언트는 그것에 맞게 REST API
를 통신하면 되고, 그것을 도와주는 것이 바로 axios
라이브러리
하지만 우리는 보통 API Server
를 직접 구축해놓거나 갖고 있지 않음. 따라서, 도움이 필요 아래의 주소 참조
https://jsonplaceholder.typicode.com/
여기서 users
정보를 받아, 웹에 출력하는 실습을 진행해보겠음
import React from "react";
import Users from "./Users";
function App() {
return <Users></Users>;
}
export default App;
App Component
은 단순히 Users
라는 Component
를 호출함
그리고 Users.js
같은 경우는 useAsync
라는 커스텀 hook을 사용하는데 이것부터 구현하겠음 useReducer
를 사용
따라서 reducer
를 첫번째로 먼저 구현
import { useReducer, useEffect, useCallback } from 'react';
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}`);
}
}
LOADING
의 경우는 현재 API
로부터 데이터를 읽어들이고 있을 때를 말함 화면에 로딩중...
이 출력될 것임
SUCCESS
의 경우, API
로부터 데이터를 읽어들이고, 받아오기에 성공했을때 dispatch
를 통해 호출됨
ERROR
의 경우 API
로부터 데이터 수집에 실패했을 경우, 호출됨
export function useAsync(callback, deps = [], skip = false) {
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: null,
});
const fetchData = useCallback(async () => {
dispatch({ type: 'LOADING' });
try {
const data = await callback();
dispatch({ type: 'SUCCESS', data });
} catch (e) {
dispatch({ type: 'ERROR', error: e });
}
}, [callback]);
useEffect(() => {
if (skip) return;
fetchData();
//eslint-disable-next-line
}, deps);
return [state, fetchData];
}
먼저 useReducer
부분 부터 설명
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: null,
});
state
의 초기값으로 객체를 넣어줬고, loading
은 false
나머지 data, error
는 null
로 초기화하고 만들어둔 reducer
함수 넣어줌
const fetchData = useCallback(async () => {
dispatch({ type: 'LOADING' });
try {
const data = await callback();
dispatch({ type: 'SUCCESS', data });
} catch (e) {
dispatch({ type: 'ERROR', error: e });
}
}, [callback]);
async
와 await
가 사용되었는데, async
함수에서는 await
가 실행되면, 그것이 끝날 때까지 다른 것들이 돌아가지 못함
여기서 callback
함수는 useAsync
Hook의 매개변수에 들어있는데, 이게 API
와 통신하는 부분으로, Error
가 발생하지 않는다면, 무사히 const data
부분에 데이터를 받아올 것이고, 그걸 dispatch
를 통해, 객체형태로 값을 받아옴
useEffect(() => {
if (skip) return;
fetchData();
//eslint-disable-next-line
}, deps);
처음부터 웹페이지를 켰을때 바로 회원정보를 불러오는 것이 아니라 버튼을 통해 불러오도록 하고 싶기 때문에, skip
이 true
면 위의 fetchData
를 실행하지 않고 false
에서만 실행
이때 deps
에 밑줄이 그어지면서 뭐가 잘못됐단 식으로 나오는데 위에
//eslint-disable-next-line
를 넣어서 없애줌 이 부분을 왜 없애야 하는지 나중에 추가하겠음
useEffect
도 아직 다루지 않았는데 이 글이 끝나는대로 다룰 예정
전부 완료 되었으면 마지막으로 [state, fetchData]
를 반환
다음으로는 Users.js
파일을 생성
import React, { useState } from 'react';
import axios from 'axios';
import { useAsync } from './useAsync';
import User from './User';
async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users',
);
return response.data;
}
function Users() {
const [state, refetch] = useAsync(getUsers, [], true);
const [userId, setUserId] = useState(null);
const { loading, data: users, error } = state;
if (loading) return <div>로딩중 ...</div>;
if (error) return <div>에러!!!</div>;
if (!users) return <button onClick={refetch}>불러오기</button>;
return (
<>
<ul>
{users.map(user => (
<li key={user.id} onClick={() => setUserId(user.id)}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
{userId && <User id={userId} />}
</>
);
}
export default Users;
하나하나 설명하겠음
async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users',
);
return response.data;
}
이 비동기 함수는 아까, fetchData
의
const data = await callback();
이 부분의 callback
에 해당하는 부분
즉, getUsers
를 통해 API
의 데이터를 받아오는 걸 알 수 있음
const [state, refetch] = useAsync(getUsers, [], true);
이렇게 호출을 하게 되면, useAsync
Hook에서 반환해주는 [state, fetchData]
가 [state, refetch]
에 들어가게 됨
이 말이 무슨 뜻이냐면, state
에는 API
와 통신되어 반환된 데이터가 들어가있다는 말이고, refetch
는 API
와 통신을 수행하는 fetchData
를 Users.js
에서 호출할 수 있다는 말이 됨
const [userId, setUserId] = useState(null);
이 부분은 단지 useState
니 설명 생략
const { loading, data: users, error } = state;
이 부분이 조금 특이한데, data: users
이렇게 해주면, state
에 들어있는 data
가 data
로 들어가는게 아니라 users
로 들어감
const x = {y: 1, z:[1,2,3]};
const {y, z: a} = x;
console.log(y); // 1
console.log(z); // Error
console.log(a); // [1,2,3]
오히려 z
를 호출하면 Error가 발생 심지어 에러 이유는 z is not defined
가 출력됨
if (loading) return <div>로딩중 ...</div>;
if (error) return <div>에러!!!</div>;
if (!users) return <button onClick={refetch}>불러오기</button>;
이 부분은 loading
이 true
일 때는, 실제로 API
에서 정보를 받아오고 있을 타이밍이므로 로딩중 ...
이 출력되고, error
가 null
이 아닐 때는 뭔가 문제가 발생했다는 의미고, !users
가 null
인 것은 아직 버튼을 만들어둔 채로 API
와 Connection을 진행하지 않았다는 의미이므로 버튼 클릭시 refetch
함수가 실행되게끔 만듦
요약하면, 문제가 발생하거나 API
와 통신 진행중의 경우는 단순히 로딩중 혹은 에러 라는 문자열이 뜨고, API
와 아직 통신진행을 하지 않았다면 불러오기
라는 문자열이 적힌 버튼이 생기고 그걸 클릭시 통신진행
return (
<>
<ul>
{users.map(user => (
<li key={user.id} onClick={() => setUserId(user.id)}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
{userId && <User id={userId} />}
</>
);
ul
로 리스트를 만들어주고, 안의 내용을 li
로 채워주는데, API
로부터 받아온 users
를 사용해서 채워줌. 고유값인 key
값도 넣어주고, li
를 클릭할 시에 유저에 관련된 정보가 뜨도록 하기 위해 useState
의 set
도 사용해줌
정보를 받아온 상태에서도 다시 불러오기
버튼의 refetch
함수를 통해 데이터를 다시 받아올 수 있음 그리고 userId
가 setUserId
에 의해 설정되었다면, User Component
를 렌더링 해줄 수 있게 됨
다음은 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]);
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;
설명 바로 들어가겠음
async function getUser(id) {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${id}`,
);
return response.data;
}
이 부분은 Users.js
에서의 getUsers
처럼 API
와 통신하는 부분인데, Users.js
에서 {userId && <User id={userId} />}
이 부분을 통해 props에 id
를 보내줬음. 때문에 그 id
를 Literal Template(`)를 통해
${id}로
User들 마다 값을 다르게 설정해
API와 통신할 수 있음 (클릭되는
li`에 따라 값이 변하니까)
즉, 아까는 유저들의 모든 정보를 받아왔다면 이번엔 단 한 명의 유저에 대한 정보만 받아옴
그 밑에 if
문 들은 전의 Users.js
와 내용이 비슷한데, 딱 하나 다른게 있음
if (!user) return null;
이 부분임
왜 다른거냐면, Users.js
는 처음에 값을 받아오지 않도록 설정하고 버튼을 통해 받아올 수 있게 했는데 이건 그냥 li
를 클릭만해도 알아서 받아오기 때문에 이 User Component
가 렌더링 되었는데 !user
라는건 불가능
return (
<div>
<h2>{user.username}</h2>
<p>
<b>Email: </b>
{user.email}
</p>
</div>
);
그래서 그렇게 받은 걸 위 처럼 출력 해주면 결과물은 다음과 같음
이 모든 내용은 velopert
님의 강좌를 보고 따라 만든 것일 뿐이므로, 정말 제대로 된 설명을 원하면
https://react.vlpt.us
에 방문하거나 패스트캠퍼스를 통해 강의를 결제하고 영상을 보길 추천