📌 Axios
axios란? node.js와 브라우저를 위한 Promise 기반 http 클라이언트이다. 즉 http를 이용해서 서버와 통신하기 위해 사용하는 패키지라고 볼 수 있다.
1) 설치하기
yarn add axios
yarn add json-server
2) db.json 파일 생성
{
"todos": [
{
"id": 1,
"title": "react"
},
{
"id": 2,
"title": "node"
},
{
"id": 3,
"title": "spring"
}
]
}
3) json-server 구동하기
json-server --watch db.json --port 4000
📌 GET (조회)
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
function App() {
// (2) 받아온 데이터를 컴포넌트 안에서 state로 쓰기 위해 state를 만들어줌
const [todos, setTodos] = useState(null);
// (1) db로부터 값을 가져오기 위한 (비동기) 함수 생성
const fetchTodos = async () => {
// await을 써주지 않으면 promise 객제 반환
// await를 통해 response를 받아옴
const response = await axios.get("http://localhost:4000/todos");
// 만들어 놓은 db는 data부분에서 찾아볼 수 있음
// 구조분해할당으로 data를 받아옴!
const { data } = await axios.get("http://localhost:4000/todos");
// (3) todos에 data를 set 해줌
// 컴포넌트 안에서 state에 db가 들어갈 수 있도록!
setTodos(data);
};
useEffect(() => {
// 최초에 마운트 될 때 db로부터 값을 가져올 것이다.
fetchTodos();
}, []);
return (
<div>
{/* 위의 로직이 돌아갈 때까지 이 부분이 기다리지 않고 먼저 실행되기 때문에
Cannot read properties of null과 같은 오류가 날 수 있음
이 부분을 해결하기 위해서는 옵셔널 체이닝(?.)을 사용함! */}
{todos?.map((item) => {
return (
<div key={item.id}>
{item.id} : {item.title}
</div>
);
})}
</div>
);
}
export default App;
🤔 옵셔널 체이닝(optinal chaining)이란?? optional chaning연산자 (?.)는 객체 내의 key에 접근할 때 그 참조가 유효한지 아닌지 직접 명시하지 않고도 접근할 수 있는 연산자이다. 만약 ?. 앞의 평가대상이 nullish ( undefined 또는 null )일 경우 평가를 멈추고 undefined를 반환한다.
📌 POST (추가)
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [todos, setTodos] = useState(null);
// (1) input 태그에 엮을 수 있는 state 만들기
const [inputValue, setInputValue] = useState({
// 초기값을 JSON 형식에 맞춰서 설정해줌
// id가 없는 이유? JSON 방식의 데이터베이스는 id 속성을 대부분 자동으로 입력 됨
title: "",
});
const fetchTodos = async () => {
const { data } = await axios.get("http://localhost:4000/todos");
setTodos(data);
};
// (4) 비동기 함수로 만들어주기
const onSubmitHandler = async () => {
// (5) POST 요청하기 (서버에 데이터 추가하기!)
axios.post("http://localhost:4000/todos", inputValue);
// (6) 새로고침을 하지 않고 바로 랜더링 될 수 있도록 하기 위해 state도 함께 변경해주기
setTodos([...todos, inputValue])
};
useEffect(() => {
fetchTodos();
}, []);
return (
<>
<div>
{/* INPUT 영역 */}
{/* (3) event.preventDefault를 통해 새로고침을 막아줌 */}
<form
onSubmit={(event) => {
event.preventDefault();
// 버튼 클릭 시, Input의 입력값(state)을 이용하여 DB에 저장(POST 요청)
onSubmitHandler();
}}
>
<input
type="text"
// (2) value와 onChange로 입력값을 state에 담아줌
value={inputValue.title}
onChange={(event) =>
setInputValue({
// 똑같이 객체 형태로 맞춰 줌
title: event.target.value,
})
}
/>
{/* form 태그 안에 버튼은 지정하지 않아도 type="submit"
submit의 고유 특성? 버튼을 누르면 항상 새로고침 됨 */}
<button>추가</button>
</form>
</div>
<div>
{/* 데이터 영역 */}
{todos?.map((item) => {
return (
<div key={item.id}>
{item.id} : {item.title}
</div>
);
})}
</div>
</>
);
}
export default App;
📌 DELETE (삭제)
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [todos, setTodos] = useState(null);
const [inputValue, setInputValue] = useState({
title: "",
});
const fetchTodos = async () => {
const { data } = await axios.get("http://localhost:4000/todos");
setTodos(data);
};
const onSubmitHandler = async () => {
axios.post("http://localhost:4000/todos", inputValue);
setTodos([...todos, inputValue]);
};
// (2) 삭제 버튼 핸들링 함수 만들기
const onDeletButtonHandler = async (id) => {
// (3) DELET 요청으로 db에서 데이터 삭제하기
axios.delete(`http://localhost:4000/todos/${id}`);
// (4) state도 변경해주기
setTodos(
todos.filter((item) => {
return item.id != id;
})
);
};
useEffect(() => {
fetchTodos();
}, []);
return (
<>
<div>
{/* INPUT 영역 */}
<form
onSubmit={(event) => {
event.preventDefault();
onSubmitHandler();
}}
>
<input
type="text"
value={inputValue.title}
onChange={(event) =>
setInputValue({
title: event.target.value,
})
}
/>
<button>추가</button>
</form>
</div>
<div>
{/* 데이터 영역 */}
{todos?.map((item) => {
return (
<div key={item.id}>
{item.id} : {item.title}
{/* (1) 삭제 버튼 추가하기 */}
{/* 어떤 것을 삭제할 지 알려주기 위해 삭제 함수를 호출 할 때 id값을 전달 */}
<button onClick={() => onDeletButtonHandler(item.id)}>
삭제
</button>
</div>
);
})}
</div>
</>
);
}
export default App;
📌 PATCH (수정)
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [todos, setTodos] = useState(null);
const [inputValue, setInputValue] = useState({
title: "",
});
// (1) 수정할 것과 관련된 state 만들기
const [targetId, setTargetId] = useState("");
const [contents, setContents] = useState("");
// 조회 함수 (GET)
const fetchTodos = async () => {
const { data } = await axios.get("http://localhost:4000/todos");
setTodos(data);
};
// 추가 함수 (POST)
const onSubmitHandler = async () => {
axios.post("http://localhost:4000/todos", inputValue);
setTodos([...todos, inputValue]);
};
// 삭제 함수 (DELETE)
const onDeleteButtonHandler = async (id) => {
axios.delete(`http://localhost:4000/todos/${id}`);
setTodos(
todos.filter((item) => {
return item.id != id;
})
);
};
// (3) 수정 함수 만들기
const onUpdateButtonHandler = async () => {
// 수정할 내용은 객체 형태로 넣어주기
axios.patch(`http://localhost:4000/todos/${targetId}`, {
title: contents,
});
// 실시간으로 변경해주기 위해서 state도 바꿔주기
setTodos(
todos.map((item) => {
if (item.id === targetId) {
return { ...item, title: contents };
} else {
return item;
}
})
);
};
useEffect(() => {
fetchTodos();
}, []);
return (
<>
<div>
{/* 수정 영역 */}
{/* (2) input 태그에 value와 onChange로 입력값을 state에 담아줌 */}
<input
type="text"
placeholder="수정할 아이디"
value={targetId}
onChange={(event) => setTargetId(event.target.value)}
/>
<input
type="text"
placeholder="수정할 내용"
value={contents}
onChange={(event) => setContents(event.target.value)}
/>
{/* (4) 수정 버튼을 클릭할 때 수정 함수 호출하기 */}
<button onClick={onUpdateButtonHandler}>수정</button>
</div>
<div>
{/* INPUT 영역 */}
<form
onSubmit={(event) => {
event.preventDefault();
onSubmitHandler();
}}
>
<input
type="text"
value={inputValue.title}
onChange={(event) =>
setInputValue({
title: event.target.value,
})
}
/>
<button>추가</button>
</form>
</div>
<div>
{/* 데이터 영역 */}
{todos?.map((item) => {
return (
<div key={item.id}>
{item.id} : {item.title}
<button onClick={() => onDeleteButtonHandler(item.id)}>
삭제
</button>
</div>
);
})}
</div>
</>
);
}
export default App;
📌 최종 코드
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [todos, setTodos] = useState(null);
const [inputValue, setInputValue] = useState({
title: "",
});
const [targetId, setTargetId] = useState("");
const [contents, setContents] = useState("");
// 조회 함수 (GET)
const fetchTodos = async () => {
const { data } = await axios.get("http://localhost:4000/todos");
setTodos(data);
};
// 추가 함수 (POST)
const onSubmitHandler = async () => {
axios.post("http://localhost:4000/todos", inputValue);
setTodos([...todos, inputValue]);
};
// 삭제 함수 (DELETE)
const onDeleteButtonHandler = async (id) => {
axios.delete(`http://localhost:4000/todos/${id}`);
setTodos(
todos.filter((item) => {
return item.id != id;
})
);
};
//수정 함수 (UPDATE)
const onUpdateButtonHandler = async () => {
axios.patch(`http://localhost:4000/todos/${targetId}`, {
title: contents,
});
setTodos(
todos.map((item) => {
if (item.id == targetId) {
return { ...item, title: contents };
} else {
return item;
}
})
);
};
useEffect(() => {
fetchTodos();
}, []);
return (
<>
<div>
{/* 수정 영역 */}
<input
type="text"
placeholder="수정할 아이디"
value={targetId}
onChange={(event) => setTargetId(event.target.value)}
/>
<input
type="text"
placeholder="수정할 내용"
value={contents}
onChange={(event) => setContents(event.target.value)}
/>
<button onClick={onUpdateButtonHandler}>수정</button>
</div>
<div>
{/* INPUT 영역 */}
<form
onSubmit={(event) => {
event.preventDefault();
onSubmitHandler();
}}
>
<input
type="text"
value={inputValue.title}
onChange={(event) =>
setInputValue({
title: event.target.value,
})
}
/>
<button>추가</button>
</form>
</div>
<div>
{/* 데이터 영역 */}
{todos?.map((item) => {
return (
<div key={item.id}>
{item.id} : {item.title}
<button onClick={() => onDeleteButtonHandler(item.id)}>
삭제
</button>
</div>
);
})}
</div>
</>
);
}
export default App;
🤔 왜 이런 오류가 뜨는걸까??
추가 함수(POST)가 끝난 다음에 state를 추가 해주게 되면 DB에는 id가 자동으로 입력이 되지만, state에는 id 값을 알 수가 없기 때문에 화면에 id가 자동으로 갱신되지 않고 새로고침을 하고 난 후에야 갱신됨
💡 해결하려면?
// 추가 함수 (POST)
const onSubmitHandler = async () => {
axios.post("http://localhost:4000/todos", inputValue);
// setTodos([...todos, inputValue]);
fetchTodos()
};
state를 추가 해주는 것이 아니라 fetchTodos() 함수를 실행하여 DB를 다시 읽어오는 방식이 더 적합할 수 있다!
📌 instance 가공하기
.env 생성하기 (환경 변수 따로 관리하기)
# 환경 정보는 따로 관리!
REACT_APP_SERVER_URL="http://localhost:4000"
src > axios > api.js 생성하기
import axios from "axios";
// .create를 통해 새로운 instance를 만들기
// 가공되지 않은 순수한 axios를 가공해주기(?)
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
});
export default instance;
App.jsx 수정하기
import "./App.css";
import { useState, useEffect } from "react";
// import axios from "axios"; (가공되지 않았던 axios)
// 가공한 axios? 콜하기
import api from "./axios/api";
function App() {
const [todos, setTodos] = useState(null);
const [inputValue, setInputValue] = useState({
title: "",
});
const [targetId, setTargetId] = useState("");
const [contents, setContents] = useState("");
// 조회 함수 (GET)
const fetchTodos = async () => {
// axios 말고 api로 바꿔주기
// baseURL를 지정해줬기 때문에 그 부분은 빼주기!
const { data } = await api.get("/todos");
setTodos(data);
};
// 추가 함수 (POST)
const onSubmitHandler = async () => {
await api.post("/todos", inputValue);
// setTodos([...todos, inputValue]);
fetchTodos();
};
// 삭제 함수 (DELETE)
const onDeleteButtonHandler = async (id) => {
api.delete(`/todos/${id}`);
setTodos(
todos.filter((item) => {
return item.id !== id;
})
);
};
//수정 함수 (UPDATE)
const onUpdateButtonHandler = async () => {
api.patch(`/todos/${targetId}`, {
title: contents,
});
setTodos(
todos.map((item) => {
if (item.id === targetId) {
return { ...item, title: contents };
} else {
return item;
}
})
);
};
useEffect(() => {
fetchTodos();
}, []);
// return문 생략
가공되지 않았던 axios를 import 하는 것이 아니라 가공한 axios를 콜한다.
axios를 api로 바꿔주고 baseURL를 지정해줬기 때문에 그 부분은 빼줘도 된다.
📌 interceptor 요청과 응답 사이에 관여하기
api.js
import axios from "axios";
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
// timeout? 서버에 통신을 요청했을 때 얼마나 기다릴지 정해주는 것
// 정해진 시간 안에 오지 않으면 오류를 낼 것! 단위는 ms
// timeout: 1,
});
// [요청].interceptors.request로 접근!
instance.interceptors.request.use(
// 요청을 보내기 전 수행되는 함수
function (config) {
console.log("인터셉터 요청 성공!");
return config;
},
// 오류 요청을 보내기 전 수행되는 함수
function (error) {
console.log("인터셉터 요청 오류!");
return Promise.reject(error);
}
);
//[응답].interceptors.response로 접근!
instance.interceptors.response.use(
// 응답을 내보내기 전 수행되는 함수
function (response) {
console.log("인터셉터 응답 성공!");
return response;
},
// 오류 응답을 내보내기 전 수행되는 함수
function (error) {
console.log("인터셉터 응답 오류!");
return Promise.reject(error);
}
);
export default instance;
interceptor를 통해
다양한 것들을 구현할 수 있다.