06. API 통신
# What? 이게 뭔데?
- Application Programming Interface, 응용 프로그램 프로그래밍 인터페이스)는 응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스를 뜻한다.
=> 백엔드랑 소통하는 통신소라 생각하면 편함.
- 프론트 : 백엔드야 사용자가 할일 저장하고싶다고 나한테 이런 데이터를 입력해줬어
- 백엔드 : 어어 그래 데이터에 문제 있는지 살펴보고 문제 없으면 데이터베이스에 저장할게. 문제 없네? 저장했어 이게 저장한 데이터야
- 프론트 : 어 고마워 나도 사용자한테 저장 잘됐다고 알려주고, 할일 목록에 할일 추가해놓을게
이런 대화를 API를 통해서 함.
# Why? 왜 쓰는데?
- 왜 이런 소통을 API를 통해서 하나? API는 모든 접속을 표준화하기 때문에 기계/운영체제 등과 상관없이 누구나 동일한 액세스를 얻을 수 있음. 즉 백엔드는 JAVA + 리눅스, 프론트는 JAVASCRIPT + 노드처럼 서로 다른 환경이라 하더라도 API를 통해서라면 문제 없이 소통 가능.
# How? 어떻게 쓰는데?
1. 가상 API 서버 세팅(실습을 위해 가상의 API 서버를 세팅)
$ npm install -g json-server
1) db.json 파일 생성 및 테스트데이터 1개 세팅해놓기(여기에 데이터들이 저장될 예정)
@db.json
{
"favorites": [
{
"id": 1,
"title": "수박",
"reason": "시원하고 달달해서 참 좋다",
}
]
}
2) API 서버 실행하기(백엔드용 서버, 프론트용 서버 두 개 열기)
$ npm start
$ json-server db.json --port=5000
2. 가상 API 서버와 통신하기 위해 axios라는 패키지 설치
$ npm install --save axios
METHOD | URL | axios로 통신 보낼 때 | |
좋아하는것 목록 | GET | /favorites | axios.get("/favorites"); |
좋아하는것 생성 (좋아하는것 = favorite) |
POST | /favorites | axios.post("/favorites", data); |
좋아하는것 수정 | PATCH or PUT | /favorites/{고칠 favorite의 id} | axios.patch("/favorites/1", data); |
좋아하는것 조회 | GET | /favorites/{보고싶은 favorite의 id} | axios.get("/favorites/1"); |
좋아하는것 삭제 | DELETE | /favorites/{삭제하고픈 favorite의 id} | axios.delete(/favorites/1"); |
3. proxy 설정
- api 서버로 통신을 보낼 때 axios("http://localhost:5000/favorites");와 같이 앞에 "http://localhost:5000" 붙여줘야함. 매번 붙여주려면 귀찮으니까 package.json의 proxy라는걸 설정해서 api를 보내는 root url을 http://localhost:5000로 고정할 수 있음. 이러면 매번 앞에 root url 안적어줘도됨.
@package.json(package.json 설정 후 npm start 다시 해줘야 변경사항 적용됨)
{
"name": "react-course",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"axios": "^0.19.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-scripts": "3.3.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:5000" // 여기가 핵심
}
# Practice 연습
1. 좋아하는것 목록을 받아와서 뿌려보기
@App.js
import React from 'react';
import Favorites from './Favorites';
function App() {
return (
<div className="App">
<Favorites />
</div>
);
}
export default App;
@ Favorites.js
import React, {useEffect, useState} from 'react';
import axios from 'axios';
import Favorite from "./Favorite";
const Favorites = () => {
let [favorites, setFavorites] = useState([]);
/* 컴포넌트가 렌더링 되면 바로 좋아하는것 목록을 불러와 favorites 데이터를 설정하기 */
useEffect(() => {
axios.get("/favorites")
.then(response => {
// 응답 데이터 형태 관찰해보기
console.log(response);
setFavorites(response.data);
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
}, []);
return (
<div>
{/* map으로 컴포넌트를 여러개 만들 때는 각 컴포넌트를 구별할 수 있는 유일한 key값을 넘겨줘야함. */}
{favorites.map(favorite => <Favorite key={favorite.id} favorite={favorite}/>)}
</div>
);
};
export default Favorites;
@ Favorite.jsx
import React, {} from 'react';
const Favorite = ({favorite}) => {
return (
<div>
<p>좋아하는 것 : {favorite.name}</p>
<p>좋아하는 이유 : {favorite.reason}</p>
</div>
);
};
export default Favorite;
2. 좋아하는것 생성해보기
@ Favorites.js
import React, {useEffect, useState} from 'react';
import axios from 'axios';
import Favorite from "./Favorite";
const Favorites = () => {
let [favorites, setFavorites] = useState([]);
let [form, setForm] = useState({
title: "", // 좋아하는것 이름
reason: "", // 좋아하는 이유
});
/* 컴포넌트가 렌더링 되면 바로 좋아하는것 목록을 불러와 favorites 데이터를 설정하기 */
useEffect(() => {
axios.get("/favorites")
.then(response => {
// 응답 데이터 형태 관찰해보기
console.log(response);
setFavorites(response.data);
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
}, []);
const changeForm = (event) => {
setForm({
...form,
[event.target.name]: event.target.value
})
};
const save = (event) => {
event.preventDefault();
axios.post("/favorites", form)
.then(response => {
// 응답 데이터 형태 관찰해보기(저장한 data를 반환해줌)
console.log(response);
// 데이터를 저장한 후, todos 목록 전체를 다시 불러올 수도 있지만,
// 기존 목록에 새로 생성된 todo만 추가해주는게 더 효율적
setFavorites([...favorites, response.data]);
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
};
return (
<div>
<form onSubmit={save}>
<div>
<input type="text" name="title" placeholder="좋아하는것" onChange={changeForm}/>
</div>
<div>
<input type="text" name="reason" placeholder="좋아하는 이유" onChange={changeForm} />
</div>
<button>좋아하는것 추가</button>
</form>
{/* map으로 컴포넌트를 여러개 만들 때는 각 컴포넌트를 구별할 수 있는 유일한 key값을 넘겨줘야함. */}
{favorites.map(favorite => <Favorite key={favorite.id} favorite={favorite}/>)}
</div>
);
};
export default Favorites;
3. 좋아하는것 수정해보기
- 좋아하는것 수정 후 새로고침 해서 데이터 업데이트됐는지 확인해보기
- 수정 후 바로 반영되게 변경해보기(setState 활용)
* 삼항연산자 알고가기
- 조건 ? (조건이 참일 때 실행할 내용) : (조건이 거짓일 때 실행할 내용)
- 예제 1) 너는 남자냐 ? 그렇다면 남탕으로 들어가 : 아니라면 여탕으로 들어가
- 예제 2) 철수가 맞는지 확인해보기
let name = "철수";
name === "철수" ? alert("나 철수 맞아") : alert("아닌데요"); // "나 철수 맞아"가 alert됨
@ Favorites.js
import React, {useEffect, useState} from 'react';
import axios from 'axios';
import Favorite from "./Favorite";
const Favorites = () => {
let [favorites, setFavorites] = useState([]);
let [form, setForm] = useState({
title: "", // 좋아하는것 이름
reason: "", // 좋아하는 이유
});
/* 컴포넌트가 렌더링 되면 바로 좋아하는것 목록을 불러와 favorites 데이터를 설정하기 */
useEffect(() => {
axios.get("/favorites")
.then(response => {
// 응답 데이터 형태 관찰해보기
console.log(response);
setFavorites(response.data);
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
}, []);
const changeForm = (event) => {
setForm({
...form,
[event.target.name]: event.target.value
})
};
const save = (event) => {
event.preventDefault();
axios.post("/favorites", form)
.then(response => {
// 응답 데이터 형태 관찰해보기(저장한 data를 반환해줌)
console.log(response);
// 데이터를 저장한 후, todos 목록 전체를 다시 불러올 수도 있지만,
// 기존 목록에 새로 생성된 todo만 추가해주는게 더 효율적
setFavorites([...favorites, response.data]);
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
};
return (
<div>
<form onSubmit={save}>
<div>
<input type="text" name="title" placeholder="좋아하는것" onChange={changeForm}/>
</div>
<div>
<input type="text" name="reason" placeholder="좋아하는 이유" onChange={changeForm} />
</div>
<button>좋아하는것 추가</button>
</form>
{/* map으로 컴포넌트를 여러개 만들 때는 각 컴포넌트를 구별할 수 있는 유일한 key값을 넘겨줘야함. */}
{favorites.map(favorite => <Favorite key={favorite.id} favorite={favorite} favorites={favorites} setFavorites={setFavorites}/>)}
</div>
);
};
export default Favorites;
@ Favorite
import React, {useState, Fragment} from 'react';
import axios from "axios";
const Favorite = ({favorite, setFavorites, favorites}) => {
let [updateMode, setUpdateMode] = useState(false); // 수정모드 여부
let [form, setForm] = useState({
title: favorite.title,
description: favorite.description
});
const changeMode = () => {
setUpdateMode(!updateMode);
};
const changeForm = (event) => {
setForm({
...form,
[event.target.name]: event.target.value
})
};
const update = (event) => {
event.preventDefault();
axios.patch("/favorites/" + favorite.id, form)
.then(response => {
// 응답 데이터 형태 관찰해보기(수정한 data를 반환해줌)
console.log(response);
// 부모로부터 받은 setFavorites 참조를 이용하여 부모의 setFavorites를 호출하여 favorites를 업데이트
setFavorites(favorites.map(favorite => {
if(favorite.id === response.data.id)
return response.data;
return favorite;
}));
setUpdateMode(false);
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
};
return (
<div>
{updateMode ?
(
<Fragment>
<div>
<input type="text" name="title" defaultValue={favorite.title} onChange={changeForm}/>
</div>
<div>
<input type="text" name="reason" defaultValue={favorite.reason} onChange={changeForm}/>
</div>
<button onClick={update}>수정완료</button>
<button onClick={changeMode}>취소</button>
</Fragment>
) :
(
<Fragment>
<p>좋아하는 것 : {favorite.title}</p>
<p>좋아하는 이유 : {favorite.reason}</p>
<button onClick={changeMode}>수정</button>
</Fragment>
)
}
</div>
);
};
export default Favorite;
4. 좋아하는것 삭제해보기
* filter 알고가기
- filter는 배열의 요소들을 하나씩 검사해보면서 조건에 안맞는 요소는 빼버린 배열을 반환해줌.
- 숫자 배열에서 짝수인 숫자는 다 빼버리기
let numbers = [1,2,3,4,5];
numbers = numbers.filter(number => {
if(number % 2 !== 0)
return number;
})
@ Favorite.js
import React, {useState, Fragment} from 'react';
import axios from "axios";
const Favorite = ({favorite, setFavorites, favorites}) => {
let [updateMode, setUpdateMode] = useState(false); // 수정모드 여부
let [form, setForm] = useState({
title: favorite.title,
description: favorite.description
});
const changeMode = () => {
setUpdateMode(!updateMode);
};
const changeForm = (event) => {
setForm({
...form,
[event.target.name]: event.target.value
})
};
const update = (event) => {
event.preventDefault();
axios.patch("/favorites/" + favorite.id, form)
.then(response => {
// 응답 데이터 형태 관찰해보기(수정한 data를 반환해줌)
console.log(response);
// 부모로부터 받은 setFavorites 참조를 이용하여 부모의 setFavorites를 호출하여 favorites를 업데이트
setFavorites(favorites.map(favorite => {
if(favorite.id === response.data.id)
return response.data;
return favorite;
}));
setUpdateMode(false);
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
};
const remove = () => {
axios.delete("/favorites/" + favorite.id)
.then(response => {
// 응답 데이터 형태 관찰해보기
console.log(response);
/* filter는 배열의 요소 중 본인이 명시한 조건을 만족하는 요소만 남긴 배열을 반환함 */
setFavorites(favorites.filter(favoriteData => {
return favoriteData.id !== favorite.id;
}));
}).catch(error => {
console.log(error);
alert("문제가 발생하였습니다.");
});
};
return (
<div>
{updateMode ?
(
<Fragment>
<div>
<input type="text" name="title" defaultValue={favorite.title} onChange={changeForm}/>
</div>
<div>
<input type="text" name="reason" defaultValue={favorite.reason} onChange={changeForm}/>
</div>
<button onClick={update}>수정완료</button>
<button onClick={changeMode}>취소</button>
</Fragment>
) :
(
<Fragment>
<p>좋아하는 것 : {favorite.title}</p>
<p>좋아하는 이유 : {favorite.reason}</p>
<button onClick={changeMode}>수정</button>
</Fragment>
)
}
<button type="button" onClick={remove}>삭제</button>
</div>
);
};
export default Favorite;
# Problem 과제
* 코드참고: https://github.com/ShinHyungJune/react-course/blob/course-6-api/src/App.js