diff --git a/Week4/LoginAndSignup/index.html b/Week4/LoginAndSignup/index.html
new file mode 100644
index 0000000..531f77d
--- /dev/null
+++ b/Week4/LoginAndSignup/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ 로그인 & 회원가입
+
+
+
+
+
+
+
diff --git a/Week4/LoginAndSignup/src/App.css b/Week4/LoginAndSignup/src/App.css
new file mode 100644
index 0000000..e69de29
diff --git a/Week4/LoginAndSignup/src/App.jsx b/Week4/LoginAndSignup/src/App.jsx
new file mode 100644
index 0000000..566c731
--- /dev/null
+++ b/Week4/LoginAndSignup/src/App.jsx
@@ -0,0 +1,15 @@
+import Router from './components/Router';
+import './App.css'
+import { GlobalStyle } from './style/GlobalStyle';
+
+function App() {
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default App
diff --git a/Week4/LoginAndSignup/src/assets/profile/profile.png b/Week4/LoginAndSignup/src/assets/profile/profile.png
new file mode 100644
index 0000000..5974b5c
Binary files /dev/null and b/Week4/LoginAndSignup/src/assets/profile/profile.png differ
diff --git a/Week4/LoginAndSignup/src/components/Router.jsx b/Week4/LoginAndSignup/src/components/Router.jsx
new file mode 100644
index 0000000..46265a1
--- /dev/null
+++ b/Week4/LoginAndSignup/src/components/Router.jsx
@@ -0,0 +1,18 @@
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+import SignUp from '../pages/SignUp/SignUp';
+import Login from '../pages/Login/Login';
+import MyPage from '../pages/MyPage/MyPage';
+
+const Router = () => {
+ return (
+
+
+ } />
+ } />
+ } />
+
+
+ );
+};
+
+export default Router;
\ No newline at end of file
diff --git a/Week4/LoginAndSignup/src/components/Toast.jsx b/Week4/LoginAndSignup/src/components/Toast.jsx
new file mode 100644
index 0000000..e541132
--- /dev/null
+++ b/Week4/LoginAndSignup/src/components/Toast.jsx
@@ -0,0 +1,32 @@
+import { useEffect } from 'react';
+import { createPortal } from 'react-dom';
+import styled from 'styled-components';
+
+const Toast = ({ error, setError, errMessage }) => {
+ useEffect(() => {
+ setTimeout(() => {
+ setError(false);
+ }, 2000);
+ }, [error]);
+
+ return createPortal(
+
+ {errMessage}
+ , document.getElementById("toast")
+ );
+};
+
+export default Toast;
+
+const ToastWrapper = styled.div`
+ background-color: var(--color-bg);
+ position: fixed;
+ bottom: 11rem;
+ right: 25rem;
+ padding: 0.6rem;
+ border-radius: 1rem;
+`;
+
+const ToastMessage = styled.div`
+ color: black;
+`;
\ No newline at end of file
diff --git a/Week4/LoginAndSignup/src/index.css b/Week4/LoginAndSignup/src/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/Week4/LoginAndSignup/src/main.jsx b/Week4/LoginAndSignup/src/main.jsx
new file mode 100644
index 0000000..54b39dd
--- /dev/null
+++ b/Week4/LoginAndSignup/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/Week4/LoginAndSignup/src/pages/Login/Login.jsx b/Week4/LoginAndSignup/src/pages/Login/Login.jsx
new file mode 100644
index 0000000..3787e65
--- /dev/null
+++ b/Week4/LoginAndSignup/src/pages/Login/Login.jsx
@@ -0,0 +1,78 @@
+import { useState } from 'react';
+import * as S from './style';
+import axios from "axios";
+import Toast from '../../components/Toast';
+import { useNavigate } from 'react-router-dom';
+
+const Login = () => {
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState(false);
+ const [errMessage, setErrMessage] = useState("");
+ const navigate = useNavigate();
+ const saveUsername = (event) => {
+ setUsername(event.target.value);
+ };
+
+ const savePassword = (event) => {
+ setPassword(event.target.value);
+ };
+
+ const moveSignupPage = () => {
+ navigate(`/signup`);
+ };
+
+ const getData = async () => {
+ try {
+ const res = await axios.post(`${import.meta.env.VITE_BASE_URL}/api/v1/members/sign-in`, {
+ username: username,
+ password: password,
+ })
+ console.log("✨성공🤩✨");
+ console.log(`아이디 : ${res.data.username}`);
+ console.log(`비번 : ${res.data.password}`);
+ navigate(`/mypage/${res.data.id}`);
+ } catch (err) {
+ setError(true);
+ setErrMessage(err.response.data.message);
+ }
+ };
+
+ return (
+ <>
+
+
+ Login
+ ID
+
+ PASSWORD
+
+
+
+ 로그인
+ 회원가입
+
+
+ {error ?
+
+ : null
+ }
+ >
+ );
+};
+
+export default Login;
\ No newline at end of file
diff --git a/Week4/LoginAndSignup/src/pages/Login/style.js b/Week4/LoginAndSignup/src/pages/Login/style.js
new file mode 100644
index 0000000..6b130dd
--- /dev/null
+++ b/Week4/LoginAndSignup/src/pages/Login/style.js
@@ -0,0 +1,77 @@
+import styled from 'styled-components';
+
+export const Container = styled.div`
+ width: 23rem;
+ height: 25rem;
+ background-color: var(--color-bg);
+ border-radius: 1rem;
+ display: inline-block;
+ flex-direction: column;
+ box-shadow: 12px 15px 63px -1px rgba(0,0,0,0.81);
+ -webkit-box-shadow: 12px 15px 63px -1px rgba(0,0,0,0.81);
+ -moz-box-shadow: 12px 15px 63px -1px rgba(0,0,0,0.81);
+`;
+
+export const PageTitle = styled.h3`
+ text-align: center;
+ font-size: 1.5rem;
+ font-weight: bold;
+ padding: 2rem;
+ margin-bottom: 1rem;
+ color: var(--color-accent);
+`;
+
+export const InputContainer = styled.div`
+ line-height: 40px;
+`;
+
+export const ButtonContainer = styled.div`
+ display: block;
+ margin-left: 4rem;
+ margin-top: 3rem;
+`;
+
+export const Field = styled.div`
+ display: inline;
+ margin-right: 1.5rem;
+ margin-left: 2rem;
+ &.id-field {
+ margin-right: 6rem;
+ }
+ &.pwd-field {
+ margin-right: 3.5rem;
+ }
+`;
+
+export const Button = styled.button`
+ display: block;
+ width: 70%;
+ margin: 1rem;
+ padding: 0.4rem;
+ border-radius: 0.5rem;
+ font-weight: bold;
+ border: solid;
+ background-color: var(--color-button-bg);
+ color: var(--color-accent);
+ &:hover {
+ background-color: var(--color-accent);
+ color: var(--color-button-bg);
+ font-weight: bold;
+ }
+`;
+
+export const SignUpBtn = styled.button`
+ display: block;
+ width: 70%;
+ margin: 1rem;
+ padding: 0.4rem;
+ border-radius: 0.5rem;
+ font-weight: bold;
+ border: solid;
+ text-align: center;
+ &:hover {
+ background-color: #000000;
+ color: #FF1493;
+ font-weight: bold;
+ }
+`;
\ No newline at end of file
diff --git a/Week4/LoginAndSignup/src/pages/MyPage/MyPage.jsx b/Week4/LoginAndSignup/src/pages/MyPage/MyPage.jsx
new file mode 100644
index 0000000..fe2bd7f
--- /dev/null
+++ b/Week4/LoginAndSignup/src/pages/MyPage/MyPage.jsx
@@ -0,0 +1,48 @@
+import { useNavigate, useParams } from 'react-router-dom';
+import axios from "axios";
+import * as S from './style';
+import { useState } from 'react';
+import profileImg from "../../assets/profile/profile.png";
+
+const MyPage = () => {
+ const { userId } = useParams();
+ const navigate = useNavigate();
+ const [username, setUsername] = useState("");
+ const [nickname, setNickname] = useState("");
+
+ const moveLoginPage = () => {
+ navigate(`/login`);
+ };
+
+ const getLoginData = async () => {
+ try {
+ axios.get(`${import.meta.env.VITE_BASE_URL}/api/v1/members/${userId}`, {
+ userId: userId,
+ }).then((response) => {
+ setUsername(response.data.username);
+ setNickname(response.data.nickname);
+ console.log("✨🔥성공🔥✨");
+
+ })
+ } catch (err) {
+ console.log(err);
+ }
+ };
+ getLoginData();
+
+ return (
+
+ MY PAGE
+
+
+ ID : {username}
+
+
+ 닉네임 : {nickname}
+
+ 로그아웃
+
+ );
+};
+
+export default MyPage;
\ No newline at end of file
diff --git a/Week4/LoginAndSignup/src/pages/MyPage/style.js b/Week4/LoginAndSignup/src/pages/MyPage/style.js
new file mode 100644
index 0000000..d231351
--- /dev/null
+++ b/Week4/LoginAndSignup/src/pages/MyPage/style.js
@@ -0,0 +1,50 @@
+import styled from 'styled-components';
+
+export const Container = styled.div`
+ width: 23rem;
+ height: 20rem;
+ line-height: 40px;
+ background-color: var(--color-bg);
+ border-radius: 1rem;
+ display: inline-block;
+ flex-direction: column;
+ -webkit-box-shadow: 0px 0px 17px 1px rgba(93,93,93,0.72);
+ box-shadow: 0px 0px 17px 1px rgba(93,93,93,0.72);
+`;
+
+export const PageTitle = styled.h3`
+ text-align: center;
+ font-size: 1.5rem;
+ padding: 2rem;
+ font-weight: bold;
+ padding-bottom: 0;
+`;
+
+export const TextArea = styled.div`
+ text-align: center;
+ background-color: var(--color-light-pink);
+ font-size: 1rem;
+ font-weight: bold;
+ padding: 0.2rem;
+`;
+
+export const Profile = styled.img`
+ width: 4rem;
+ height: auto;
+ margin-left: 9.5rem;
+`;
+
+export const Button = styled.button`
+ display: block;
+ width: 70%;
+ margin-left: 3.5rem;
+ margin-top: 1.5rem;
+ padding: 0.4rem;
+ border-radius: 0.5rem;
+ border: solid;
+ &:hover {
+ background-color: var(--color-button-bg);
+ color: var(--color-light-pink);
+ font-weight: bold;
+ }
+`;
diff --git a/Week4/LoginAndSignup/src/pages/Signup/Signup.jsx b/Week4/LoginAndSignup/src/pages/Signup/Signup.jsx
new file mode 100644
index 0000000..74e0994
--- /dev/null
+++ b/Week4/LoginAndSignup/src/pages/Signup/Signup.jsx
@@ -0,0 +1,156 @@
+import { GlobalStyle } from '../../style/GlobalStyle'
+import axios from "axios";
+import { useCallback, useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import * as S from './style';
+
+const SignUp = () => {
+ const [username, setUsername] = useState('');
+ const [nickname, setNickname] = useState('');
+ const [password, setPassword] = useState('');
+ const [isExist, setIsExist] = useState('none');
+ const [isPasswordConfirm, setIsPasswordConfirm] = useState(false);
+ const [signupButton, setSignupButton] = useState(false);
+ const navigate = useNavigate();
+
+ const saveUsername = event => {
+ setUsername(event.target.value);
+ };
+
+ const savePassword = event => {
+ setPassword(event.target.value);
+ };
+
+ const saveNickName = event => {
+ setNickname(event.target.value);
+ };
+
+ const moveLoginPage = () => {
+ navigate(`/login`);
+ };
+
+ const postData = async () => {
+ try {
+ axios.post(`${import.meta.env.VITE_BASE_URL}/api/v1/members`, {
+ "username": username,
+ "nickname": nickname,
+ "password": password
+ }).then(() => {
+ console.log("성공🤩");
+ console.log(`아이디 : ${username}`);
+ console.log(`비번 : ${password}`);
+ console.log(`닉네임 : ${nickname}`);
+ })
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ const duplicationCheck = () => {
+ let inputID = document.querySelector(".id-input").value;
+
+ axios.get(`${import.meta.env.VITE_BASE_URL}/api/v1/members/check`, {
+ params: {
+ "username": `${inputID}`,
+ },
+ })
+ .then((response) => {
+ const isDuplicate = response.data.isExist;
+ console.log(isDuplicate);
+ if (isDuplicate) {
+ setIsExist('exist');
+ console.log("중복되는 아이디 입니다.");
+ } else {
+ setUsername(inputID);
+ setIsExist('notExist');
+ console.log("🔥사용 가능한 아이디입니다.🔥");
+ }
+ })
+ .catch(function (error) {
+ console.log(error);
+ });
+ };
+
+ const onChangePasswordConfirm = useCallback(
+ (e) => {
+ const passwordConfirmCurrent = e.target.value
+ if (password === passwordConfirmCurrent) {
+ console.log('✅비밀번호 일치✅');
+ setIsPasswordConfirm(true)
+ } else {
+ console.log('🚨비밀번호 불일치🚨');
+ setIsPasswordConfirm(false)
+ }
+ },
+ [password]
+ )
+
+ useEffect(() => {
+ username && isExist === 'notExist' && isPasswordConfirm && nickname ? (
+ setSignupButton(true)
+ ) : (
+ setSignupButton(false)
+ )
+ }, [username, isExist, isPasswordConfirm, nickname]);
+
+ return (
+ <>
+
+
+ Sign Up
+ ID
+ {
+ saveUsername(e);
+ setIsExist('none');
+ }} />
+ {
+ saveUsername(event);
+ duplicationCheck();
+ }}
+ >중복체크
+
+ 비밀번호
+
+ 비밀번호 확인
+
+ 닉네임
+
+ {
+ postData();
+ moveLoginPage();
+ }}>
+ 회원가입
+
+
+ >
+ );
+};
+
+export default SignUp;
+
diff --git a/Week4/LoginAndSignup/src/pages/Signup/style.js b/Week4/LoginAndSignup/src/pages/Signup/style.js
new file mode 100644
index 0000000..dd948c3
--- /dev/null
+++ b/Week4/LoginAndSignup/src/pages/Signup/style.js
@@ -0,0 +1,92 @@
+import styled from 'styled-components';
+
+export const Container = styled.div`
+ width: 23rem;
+ height: 25rem;
+ line-height: 40px;
+ background-color: var(--color-bg);
+ border-radius: 1rem;
+ display: inline-block;
+ flex-direction: column;
+ box-shadow: 12px 15px 63px -1px rgba(0,0,0,0.81);
+ -webkit-box-shadow: 12px 15px 63px -1px rgba(0,0,0,0.81);
+ -moz-box-shadow: 12px 15px 63px -1px rgba(0,0,0,0.81);
+`;
+
+export const PageTitle = styled.h3`
+ text-align: center;
+ font-size: 1.5rem;
+ color: var(--color-accent);
+ padding: 2rem;
+ font-weight: bold;
+`;
+
+export const Field = styled.div`
+ display: inline;
+ margin-right: 1.5rem;
+ margin-left: 2rem;
+ &.id-field {
+ margin-right: 6rem;
+ }
+ &.pwd-field {
+ margin-right: 3.5rem;
+ }
+ &.pwd-error {
+ margin-right: 1.5rem;
+ }
+ &.pwd-success > input {
+ margin-right: 1.5rem;
+ border-color: green;
+ }
+ &.nickname-field {
+ margin-right: 4.35rem;
+ }
+`;
+
+export const Input = styled.input`
+ padding: 0.28rem;
+ border-radius: 0.5rem;
+ border: solid;
+`;
+
+export const CheckButton = styled.button`
+ display: inline;
+ margin: 0.5rem;
+ border-radius: 0.7rem;
+ padding: 0.28rem;
+ font-weight: bold;
+ border: solid;
+ &.id-notExist {
+ background-color: green;
+ }
+ &.id-exist {
+ background-color: red;
+ }
+ &.none {
+ background-color: black;
+ color: var(--color-accent);
+ font-weight: normal;
+ }
+`
+export const SignUpBtn = styled.button`
+ display: block;
+ width: 60%;
+ margin: auto;
+ margin-top: 3rem;
+ padding: 0.4rem;
+ border-radius: 0.5rem;
+ font-weight: bold;
+ border: solid;
+ text-align: center;
+ background-color: var(--color-button-bg);
+ color: var(--color-accent);
+ &:hover {
+ background-color: var(--color-accent);
+ color: var(--color-button-bg);
+ font-weight: bold;
+ }
+ &:disabled {
+ opacity: 0.4;
+ pointer-events: none; // disabled 되었을 땐 hover효과 없음
+ }
+`;
diff --git a/Week4/LoginAndSignup/src/style/GlobalStyle.jsx b/Week4/LoginAndSignup/src/style/GlobalStyle.jsx
new file mode 100644
index 0000000..78a51e3
--- /dev/null
+++ b/Week4/LoginAndSignup/src/style/GlobalStyle.jsx
@@ -0,0 +1,44 @@
+import { createGlobalStyle } from "styled-components";
+import reset from "styled-reset";
+
+export const GlobalStyle = createGlobalStyle`
+${reset}
+
+:root {
+ --color-bg: #f5f5f5;
+ --color-button-bg: #000000;
+ --color-light-pink: #FFD2D7;
+ --color-accent: #FF1493;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: rgb(219, 221, 223); /* gradient CSS*/
+}
+
+button {
+ cursor: pointer;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+input {
+ padding: 0.28rem;
+ border-radius: 0.5rem;
+ border: solid;
+}
+
+`;
+
+
diff --git a/Week4/LoginAndSignup/src/style/thinkHW/image.png b/Week4/LoginAndSignup/src/style/thinkHW/image.png
new file mode 100644
index 0000000..59463c4
Binary files /dev/null and b/Week4/LoginAndSignup/src/style/thinkHW/image.png differ
diff --git a/Week4/LoginAndSignup/src/style/thinkHW/week4.md b/Week4/LoginAndSignup/src/style/thinkHW/week4.md
new file mode 100644
index 0000000..f8ae686
--- /dev/null
+++ b/Week4/LoginAndSignup/src/style/thinkHW/week4.md
@@ -0,0 +1,47 @@
+## 🖤 API 통신에 대하여
+
+### ➰ 에러 / 로딩 처리를 하는 방법에는 어떤 것들이 있을까?
+> 에러 처리 방법
+처리 방법을 따지기 전에 막는 방법 먼저 살펴보자.
+##### 에러 전파 최대한 막기
+- try catch문과 throw 구문을 예로 들면, 이들은 에러가 발생한 곳에서 에러 처리에 대한 권한을 가장 먼저 갖고, 처리할 수 없는 경우 다른 곳으로 처리를 위임하게 된다. => `에러 전파`
+- 에러가 전파되는 3가지의 레이어를 정의하자면 다음과 같을 것이다.
+ 1. 에러가 최초로 발생한 함수 (e.g. fetch)
+ 2. 1번 함수의 반환값을 표현하는 컴포넌트
+ 3. 2번 컴포넌트를 감싸는 에러 바운더리
+
+#### 🔍 React ErrorBoundary (에러 처리 방법)
+- ErrorBoundary는 데이터를 가져올 때 에러가 발생하면 그 에러에 대한 핸들링 처리를 위임 받을 수 있는 컴포넌트이다.
+- try-catch문처럼 동작한다.
+
+#### 🔍 React Suspense (로딩 처리 방법)
+- Suspense는 데이터를 가져올 때 데이터의 준비가 끝나지 않았을 때에는 컴포넌트를 렌더링하지 않고 지정한 컴포넌트를 보여줄 수 있는 컴포넌트를 의미한다.
+- children으로 들어간 컴포넌트가 비동기 처리할 때의 처리를 외부인 Suspense로 위임 받을 수 있다.
+
+
+### ➰ 패칭 라이브러리란 무엇이고 어떤 것들이 있을까?
+
+
+
+- React와 함께 사용 가능한 데이터 패칭 라이브러리는 axios, swr, tanstack query(전 react-query), Redux Toolkit Query, Apollo Client가 있다.
+
+
+### ➰ 패칭 라이브러리를 쓰는 이유는 무엇일까?
+> Server State 분리의 필요성
+
+*Server State: 서버로부터 받아오는 state(e.g. 비동기 로직을 통해 세팅하는 state)
+- redux에 server state를 같이 관리하게 되면서 store가 점점 비대해지고, 관심사의 분리가 어렵게 되는 문제들이 발생할 수 있다.
+- 또한 DB에 있는 자료들을 프론트에서 렌더링하기 위해 임시적으로 redux store에 자료들을 보관하는 용도인데, 시간이 지날수록 실제 DB의 자료와 redux store에 보관된 자료들의 일관성이 깨질 수 있다.
+-> 패칭 라이브러리가 데이터의 일관성 유지를 대신 수행해준다.
+
+- 간편한 비동기 데이터 요청
+- 상태관리 및 업데이트 용이
+- 캐싱과 최적화
+- 로딩, 오류 처리
+- 서버사이드렌더링(SSR)과의 호환
+
+
+출처
+https://velog.io/@0715yk/FE-Without-Redux-MiddleWares
+https://velog.io/@diso592/%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%A8%EC%B9%AD-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%EA%B2%BD%EA%B3%BC-%EA%B0%9C%EB%85%90
+
diff --git a/Week4/LoginAndSignup/thinkHW/image.png b/Week4/LoginAndSignup/thinkHW/image.png
new file mode 100644
index 0000000..59463c4
Binary files /dev/null and b/Week4/LoginAndSignup/thinkHW/image.png differ
diff --git a/Week4/LoginAndSignup/thinkHW/week4.md b/Week4/LoginAndSignup/thinkHW/week4.md
new file mode 100644
index 0000000..f8ae686
--- /dev/null
+++ b/Week4/LoginAndSignup/thinkHW/week4.md
@@ -0,0 +1,47 @@
+## 🖤 API 통신에 대하여
+
+### ➰ 에러 / 로딩 처리를 하는 방법에는 어떤 것들이 있을까?
+> 에러 처리 방법
+처리 방법을 따지기 전에 막는 방법 먼저 살펴보자.
+##### 에러 전파 최대한 막기
+- try catch문과 throw 구문을 예로 들면, 이들은 에러가 발생한 곳에서 에러 처리에 대한 권한을 가장 먼저 갖고, 처리할 수 없는 경우 다른 곳으로 처리를 위임하게 된다. => `에러 전파`
+- 에러가 전파되는 3가지의 레이어를 정의하자면 다음과 같을 것이다.
+ 1. 에러가 최초로 발생한 함수 (e.g. fetch)
+ 2. 1번 함수의 반환값을 표현하는 컴포넌트
+ 3. 2번 컴포넌트를 감싸는 에러 바운더리
+
+#### 🔍 React ErrorBoundary (에러 처리 방법)
+- ErrorBoundary는 데이터를 가져올 때 에러가 발생하면 그 에러에 대한 핸들링 처리를 위임 받을 수 있는 컴포넌트이다.
+- try-catch문처럼 동작한다.
+
+#### 🔍 React Suspense (로딩 처리 방법)
+- Suspense는 데이터를 가져올 때 데이터의 준비가 끝나지 않았을 때에는 컴포넌트를 렌더링하지 않고 지정한 컴포넌트를 보여줄 수 있는 컴포넌트를 의미한다.
+- children으로 들어간 컴포넌트가 비동기 처리할 때의 처리를 외부인 Suspense로 위임 받을 수 있다.
+
+
+### ➰ 패칭 라이브러리란 무엇이고 어떤 것들이 있을까?
+
+
+
+- React와 함께 사용 가능한 데이터 패칭 라이브러리는 axios, swr, tanstack query(전 react-query), Redux Toolkit Query, Apollo Client가 있다.
+
+
+### ➰ 패칭 라이브러리를 쓰는 이유는 무엇일까?
+> Server State 분리의 필요성
+
+*Server State: 서버로부터 받아오는 state(e.g. 비동기 로직을 통해 세팅하는 state)
+- redux에 server state를 같이 관리하게 되면서 store가 점점 비대해지고, 관심사의 분리가 어렵게 되는 문제들이 발생할 수 있다.
+- 또한 DB에 있는 자료들을 프론트에서 렌더링하기 위해 임시적으로 redux store에 자료들을 보관하는 용도인데, 시간이 지날수록 실제 DB의 자료와 redux store에 보관된 자료들의 일관성이 깨질 수 있다.
+-> 패칭 라이브러리가 데이터의 일관성 유지를 대신 수행해준다.
+
+- 간편한 비동기 데이터 요청
+- 상태관리 및 업데이트 용이
+- 캐싱과 최적화
+- 로딩, 오류 처리
+- 서버사이드렌더링(SSR)과의 호환
+
+
+출처
+https://velog.io/@0715yk/FE-Without-Redux-MiddleWares
+https://velog.io/@diso592/%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%A8%EC%B9%AD-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%EA%B2%BD%EA%B3%BC-%EA%B0%9C%EB%85%90
+