🔷 브라우저나 Node.js에서 사용 가능한 HTTP 클라이언트 라이브러리
// 간단한 명령어로 axios 추가하기
yarn add axios
🔷 axios 실습을 위해 알아두어야 할 것들이 있다.
JSON PlaceHolder
useReducer
useState: setState를 통해 현재의 State를 변환
useReducer: 다양한 로직이 들어간 reducer 함수를 이용해 현재의 State를 변환
❗ 하지만 useReducer로는 async await를 통한 네트워크 호출을 할 수 없다. 상태만을 관리하며 컴포넌트가 순수해야하기 때문인데, 왜 순수해야 하는지에 대해서는 뒤에 설명한다.
React Router
😭 그런데...
프로그래머스 강의 상으로는 Router V5를 사용하고 있지만 현재는 V6를 사용한다.
그리고... V6부터 바뀐 것이 상당히 많아 강의를 따라가도 의미가 없다. ㅜㅜ
본격적인 Router 사용은 추후에 기회가 닿을 때 해보기로 한다.
이전에 제작해둔 컴포넌트와 훅들
// 바뀐 import 예시
import { Header } from './components';
import { useAsync } from './hooks';
🔷 axios 실습 코드
💻 src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
💻 contexts/PostProvider.js
import { createContext, useCallback, useContext, useEffect, useReducer } from "react";
const PostContext = createContext();
export const usePostContext = () => useContext(PostContext);
const reducer = (state, action) => {
switch (action.type) {
case 'INIT_POSTS': {
return action.payload
}
case 'ADD_POST': {
return [...state, action.payload]
}
case 'DELETE_POST': {
const payload = action.payload;
return state.filter(item => item.id !== payload.id);
}
default: {
console.error('Wrong type');
break;
}
}
}
const PostProvider = ({ children, initialPosts, handleDeletePost, handleAddPost }) => {
const [posts, dispatch] = useReducer(reducer, initialPosts || []);
useEffect(() => {
dispatch({ type: 'INIT_POSTS', payload: initialPosts || [] });
}, [initialPosts]);
const onAddPost = useCallback(async (post) => {
const payload = await handleAddPost(post);
dispatch({ type: 'ADD_POST', payload });
}, [handleAddPost])
const onDeletePost = useCallback(async (id) => {
const payload = await handleDeletePost(id);
dispatch({ type: 'DELETE_POST', payload });
}, [handleDeletePost])
return (
<PostContext.Provider value={{ posts, onDeletePost, onAddPost }}>
{children}
</PostContext.Provider>
)
};
export default PostProvider;
Prop Driiling 발생을 막기 위해 사용하는 context API이다.
Context API (클릭)
포스트 생성, 추가, 삭제를 이용한 useReducer 로직이다. 해당 메서드들은 JSON Placeholder 사이트에서 알 수 있다.
💻 domain/PostAddForm/index.js
import { usePostContext } from "../../../contexts/PostProvider";
import { useForm } from "../../../hooks";
import Spinner from "../../base/Spinner";
const PostAddForm = () => {
const { onAddPost } = usePostContext();
const { isLoading, errors, handleChange, handleSubmit } = useForm({
initialValues: {
userId: 1,
title:'',
body: ''
},
onSubmit: async (values) => {
await onAddPost(values);
},
validate: ({ title, body }) => {
const errors = {};
if (!title) errors.title = "제목을 입력해주세요."
if (!body) errors.body = "내용을 입력해주세요."
return errors;
}
});
return (
<form onSubmit={handleSubmit}>
<div>
<input type="text" name="title" onChange={handleChange}/>
{errors.title}
</div>
<div>
<input type="text" name="body" onChange={handleChange} />
{errors.body}
</div>
{isLoading ? <Spinner /> : <button type="submit">Add</button>}
</form>
)
}
export default PostAddForm;
💻 domain/PostItem/index.js
import { useCallback, useState } from "react";
import { Header, Spinner, Text } from "../..";
import { usePostContext } from "../../../contexts/PostProvider";
const PostItem = ({ post }) => {
const [loading, setLoading] = useState(false);
const { onDeletePost } = usePostContext();
const handleDeletePost = useCallback(async (id) => {
setLoading(true);
await onDeletePost(id);
setLoading(false);
}, [onDeletePost]);
return (
<li>
<Header strong level={3}>
{post.title}
</Header>
<Text>{post.body}</Text>
<div>
{loading ? (
<Spinner />
) : (
<button onClick={() => handleDeletePost(post.id)}>Delete</button>
)}
</div>
</li>
)
}
export default PostItem;
💻 domain/PostList/index.js
import { usePostContext } from "../../../contexts/PostProvider";
import PostItem from "../PostItem";
const PostList = () => {
const { posts } = usePostContext();
return (
<ul>
{
posts.map((post) => (
<PostItem key={post.id} post={post} />
))
}
</ul>
)
}
export default PostList;
💻 App.js
import axios from 'axios';
import { useAsync } from './hooks';
import { Header } from './components';
import { Spinner } from './components';
import PostList from './components/domain/PostList';
import PostProvider from './contexts/PostProvider';
import { useCallback } from 'react';
import PostAddForm from './components/domain/PostAddForm';
const App = () => {
const initialPosts = useAsync(async () => {
return await axios
.get('https://jsonplaceholder.typicode.com/posts')
.then(response => response.data);
}, []);
const handleDeletePost = useCallback(async (id) => {
return await axios.delete(`https://jsonplaceholder.typicode.com/posts/${id}`).then(() => ({ id }));
}, []);
const handleAddPost = useCallback(async (post) => {
return await axios.post(`https://jsonplaceholder.typicode.com/posts`, post).then((response) => response.data);
}, []);
return (
<PostProvider
initialPosts={initialPosts.value}
handleDeletePost={handleDeletePost}
handleAddPost={handleAddPost}
>
<div>
<Header>Posts</Header>
<PostAddForm></PostAddForm>
{initialPosts.isLoading ? <Spinner /> : <PostList />}
</div>
</PostProvider>
);
}
export default App;
🖨 완성
이상으로 axios를 실습해보았다.