1. ๋งฅ๋ฝ ์ดํดํ๊ธฐ ์ฌ์ด ๋ณ์๋ช ์ผ๋ก ํต์ผ
์ฐธ๊ณ
- ํ์
๋ณ ๋ณ์๋ช
- boolean : is-, has-, can-, ...
- function : get-, handle-, submit-, …
- array : -s (ex. users.map(user ⇒ user.id)), …
- ํผํด์ผ ํ ๋ณ์๋ช
- data, info, foo, user1, mdhms, …
์ด๋ฒคํธ ํจ์๋ api ํจ์๋ช ์ ~ handler๋ก ํต์ผํ์ต๋๋ค.
onSubmit => submitHandler
changeButton => changeButtonHandler
handleEdit => editHandler
handleDelete => deleteHandler
2. typescript์์ 'any' ์์ ๊ธฐ
Axios์ interceptors ๋ผ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ง์ ์ ๊ฐ๋ก์ฑ์ ๊ณตํต์ ์ผ๋ก ํ์ํ ํค๋์ ํ ํฐ์ ๋ฃ์ด์ฃผ์๋ค.
์ด๋ config๋ฅผ any๋ก ์ค์ ํ์๋ค.
any๋ฅผ ์์ ๊ณ ์ ๊ตฌ๊ธ๋ง์ ํ ๊ฒฐ๊ณผ, header๊ฐ AxiosRequestConfig๋ฅผ ๊ฐ๋๋ค๋ ๊ฒ์ ์๊ฒ ๋์๋ค.
axios.create๋ก ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ , AxiosRequestConfig ํ์ ์ ๊ฐ์ง๋ config ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ๋๊ฒจ์ฃผ์๋ค.
๐ซ ์์ ์ ์ฝ๋
import axios from "axios";
const axiosInstance = axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
axiosInstance.interceptors.request.use(
async (config: any) => {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`; //์ฌ๊ธฐ๋ accessToken
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default axiosInstance;
โก๏ธ์์ ํ ์ฝ๋
import axios, { AxiosRequestConfig } from "axios";
const axiosInstance = axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
axiosInstance.interceptors.request.use(
async (config: AxiosRequestConfig) => {
const token = localStorage.getItem("token");
if (token) {
config.headers = {};
config.headers.Authorization = `Bearer ${token}`; //์ฌ๊ธฐ๋ accessToken
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default axiosInstance;
3. typescript์์ 'any' ์์ ๊ธฐ (2)
react-query๋ฅผ ์ด๋ฒ ์๊ฐ์ ํตํด์ ์ฒ์ ์ฌ์ฉํด๋ณด์๋๋ฐ typescript์ ํจ๊ป ์ฐ๋ ์ค๋ฅ๋ฅผ ๋ ๋ง์ด ๋ง๋ฌ๋ค.
๋ค๋ฅธ ๋ถ๋ค์ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํด๋ณด๋ค๊ฐ ๋ง์ ๋ถ๋ค์ด useQuery๋ useMutation์ผ๋ก ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ hook์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋ณด์๊ณ hook์ฒ๋ฆฌํ๋ ๊ฒ์ด ๊น๋ํ๋ค๊ณ ํ๋จํด์ ๋ด ์ฝ๋์๋ ์ ์ฉํด๋ณด์๋ค.
useMutation์ผ๋ก ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ onSuccess์, onError์ธ ์ํ๋ก ๋๋๊ณ ์๋๋ฐ ์ด๋ ๋ฐ์ดํฐ(loginData) ํ์ ์
export interface AuthResponse { message: string; token: string; }
์ด๋ ๊ฒ ์ค์ ํด์ ๋ฃ์ด์ฃผ๊ณ ์๋๋ฐ ์๋ฌ๊ฐ ๋๊ณ ์์๋ค.
์ฐธ๊ณ ๋ก onSuccess์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์์ ์ฐ์ด๋ณด๋ฉด ์๋์ ๊ฐ์ด ๋์์ AuthResponse๋ฅผ ์์ ๊ฐ์ด ์ค์ ํ์ต๋๋ค.
{message: '์ฑ๊ณต์ ์ผ๋ก ๋ก๊ทธ์ธ ํ์ต๋๋ค', token: 'eyJhbGciOiJIUzI1NiJ9.dGVzdEB0ZXN0LmNvbQ.ryq30DukSEV3o-wUkSBPVgBNQ8eBN0FO5Hog7vjsUiI'}
์๋ฌ ์ฝ๋
TS2769: No overload matches this call. Overload 1 of 4, '(mutationFn: MutationFunction<AuthResponse, UserInfo>, options?: Omit<UseMutationOptions<AuthResponse, any, UserInfo, unknown>, "mutationFn"> | undefined): UseMutationResult<...>', gave the following error.
๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ
์ ์ฝ๋์์ useMutation์ ๋ด๋ถ ์ธ์๋ค์ ๋ณด๊ณ ํ์ ์ ์ถ๋ก ํ๊ฒ ๋๋๋ฐ, ์ฒซ ๋ฒ์งธ ์ธ์์ธ mutationFn์ ํ์ ์ ๋ณด๋ฉด AuthAPI.login(userInfo)์ ํ์ ์ด (data: UserInfo) => any๋ก ์ถ์ ๋๋ ๊ฑธ ๋ณผ ์ ์์๋ค.
๊ทธ๋ฌ๋ฉด onSuccess๋ก ๋์ด๊ฐ๋ ๋ฐ์ดํฐ์ ํ์ ์ any ํ์ ์ด ๋์ด๊ฐ๋ ๊ฑธ ๊ธฐ๋ํ ํ ๋ฐ AuthResponse ํ์ ์ด๋ผ๊ณ ํ๋๊น ํ์ ์๋ฌ๋ฅผ ๋ด๋๊ฒ์ด์๋ค!
์ฆ, AuthAPI.login์ ํ์ ์ (data: UserInfo) => AuthResponse ํ์ ์ผ๋ก ๋ง๋ค๋ฉด ์๋ฌ๊ฐ ์ฌ๋ผ์ง๋ค!
๋ด๋ถ ์ธ์์ ํ์ ์ ํ์ธํด์ ๋ฆฌํดํ๋ ํ์ ์ ๋ช ์์ ์ผ๋ก ์ค์ ํด์ฃผ์ด์ผํ๋ค!
๊ธฐ์กด ์ฝ๋
//AuthAPI
import axiosInstance from "./axiosInstance";
import { UserInfo } from "../types/model";
const AuthAPI = {
signUp: (data: UserInfo) => {
return axiosInstance.post("/users/create", data);
},
login: (data: UserInfo) => {
return axiosInstance.post("/users/login", data);
},
};
export default AuthAPI;
์์ ์ฝ๋
//AuthAPI
import axiosInstance from "./axiosInstance";
import { UserInfo, AuthResponse } from "../types/model";
const AuthAPI = {
signUp: (data: UserInfo) => {
return axiosInstance.post("/users/create", data);
},
login: (data: UserInfo): Promise<AuthResponse> => {
return axiosInstance.post("/users/login", data);
},
};
export default AuthAPI;
4. ๋ฆฌ์กํธ ์ฟผ๋ฆฌ ์ ์ฉ๊ณผ ๊ด์ฌ์ฌ ๋ถ๋ฆฌํ๊ธฐ
axios์ interceptors๋ฅผ ์ฌ์ฉํด์ baseURL๊ณผ ํค๋์ ๋ค์ด๊ฐ ํ ํฐ์ ์ธํ ํ๋ค.
๊ทธ๋ฆฌ๊ณ authํํธ์ todoํํธ๋ฅผ ๋๋์ด์ interceptors๋ฅผ ์ฌ์ฉํ๋ api๋ฅผ ๊ฐ๊ฐ ๋ง๋ค์๋ค.
// AuthAPI
import axiosInstance from "./axiosInstance";
import { UserInfo, AuthResponse } from "../types/model";
const AuthAPI = {
signUp: (data: UserInfo) => {
return axiosInstance.post("/users/create", data);
},
login: (data: UserInfo): Promise<AuthResponse> => {
return axiosInstance.post("/users/login", data);
},
};
export default AuthAPI;
๊ทธ๋ฆฌ๊ณ login, signup, get, post, put, delete hook์ ๋ง๋ค์ด์ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ๋ฅผ ํด๋ณด์๋ค.
์ฌ๊ธฐ์๋ get ๋ฉ์๋์๋ useQuery๋ฅผ, ๊ทธ ์ธ์๋ useMutation๋ฅผ ์ฌ์ฉํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ด hook์ด ํ์ํ ๊ณณ์์ ํธ์ถ๋ง ํ๋ฉด ๋ฐ์ดํฐ๊ฐ ์ง !๋์จ๋ค.
ํนํ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ useGetTodo hook์์๋ ๋ก๋ฉ ์ํ ์ ๋ฌด๋ฅผ ๋น ๋ฅด๊ฒ ํ๋จํด์ ๋ก๋ฉ ์ํ๋ก ์์ฝ๊ฒ ๊ตฌํํ ์ ์์๋ค!
refactoring ์
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { TodoMD } from "../types/model";
import SingleTodo from "./SingleTodo";
import axiosInstance from "../api/axiosInstance";
interface Prop {
submitHandler: (e: React.FormEvent<HTMLFormElement>) => void;
}
const TodoList = ({ submitHandler }: Prop) => {
const [getData, setGetData] = useState<TodoMD[]>([]);
useEffect(() => {
axiosInstance.get("/todos").then((res) => setGetData(res.data));
}, [submitHandler]);
return (
<Container>
{getData?.map((todo: TodoMD) => (
<SingleTodo key={todo.id} {...todo} />
))}
</Container>
);
};
export default TodoList;
๐ useState๋ฅผ ์ฌ์ฉํด์ ์ํ๊ด๋ฆฌ๋ฅผ ํด์ฃผ๊ณ ์๋ค.
refactoring ํ
import React from "react";
import styled from "styled-components";
import { TodoMD } from "../types/model";
import SingleTodo from "./SingleTodo";
import useGetToDo from "../hooks/todos/useGetTodo";
import LoadingBar from "../components/LodingBar";
const TodoList = () => {
const { data, isLoading } = useGetToDo();
return (
<Container>
<LoadingBar isLoading={isLoading} />
{data?.map((todo: TodoMD) => (
<SingleTodo key={todo.id} {...todo} />
))}
</Container>
);
};
export default TodoList;
๐ useGetToDo hook์ ์ฌ์ฉํด์ ๊ฐ๋จํ๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๊ณ , ๋ก๋ฉ์ํ์ธ์ง ์๋์ง๋ฅผ ํ๋จํด์ ๋ก๋ฉ์ค์ด๋ฉด ๋ก๋ฉ ์ํ๋ฅผ ํ์ํ๋ค.
5. ๊ด์ฌ์ฌ ๋ถ๋ฆฌํ๊ธฐ (2)
auth ์๊ตฌ์ฌํญ์ด ์๋์ ๊ฐ์๋ค.
- ์ด๋ฉ์ผ ์กฐ๊ฑด : ์ต์ @, . ํฌํจ
- ๋น๋ฐ๋ฒํธ ์กฐ๊ฑด : 8์ ์ด์ ์ ๋ ฅ
- ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๊ฐ ๋ชจ๋ ์ ๋ ฅ๋์ด ์๊ณ , ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ์ ์ถ ๋ฒํผ์ด ํ์ฑํ ๋๋๋ก ํด์ฃผ์ธ์
refactoring ์
//Auth.tsx
const changeButtonHandler = () => {
email.includes("@") && password.length >= 8
? setIsActive(true)
: setIsActive(false);
๐ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ input ์์ onKeyUp ์ด๋ฒคํธ๋ฅผ ํ์ฉํ๋ ์์ผ๋ก ํ๋ค. (๊ด์ฌ์ฌ ๋ถ๋ฆฌ X)
refactoring ํ
//Auth.tsx
import { emailCheck, passwordCheck } from "../../utils/validator";
const validatorHandler = () => {
if (emailCheck(email) && passwordCheck(password)) {
setIsActive(true);
} else {
setIsActive(false);
}
};
// validator.ts
function emailCheck(email: string): boolean {
if (email.includes("@") && email.includes(".")) {
return true;
} else {
alert("์ฌ๋ฐ๋ฅธ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.");
return false;
}
}
function passwordCheck(password: string) {
if (password.length >= 8) {
return true;
}
}
export { emailCheck, passwordCheck };
๐ auth์ ์๊ตฌ์ฌํญ์ ํ์ธํ๋ ๋ถ๋ถ์ validator.ts ํจ์๋ก ๋ฐ๋ก ๋ถ๋ฆฌํ์๋ค.
๊ทธ๋์ ์ถ๊ฐ์ ์ธ ์๊ตฌ์ฌํญ์ด ์๊ธฐ๊ฑฐ๋ ๋ฌธ์ ์ฌํญ์ด ๋ฐ์ํ์๋ ์ฝ์ด์ผ ํ๋ ์ฝ๋์ ๋จ์๊ฐ ์ค์ด๋ค์ด์ ์ฝ๋์ ๋ํ ํ์ ์ด ๋นจ๋ผ์ง๋ฏ๋ก ๋ฌธ์ ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํด๊ฒฐํ ์ ์๋ค!!
'๐ฅธReview > ํ๋ก์ ํธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๊ฐ์ธ ํฌํธํด๋ฆฌ์ค ํ๋ก์ ํธ ํ๊ธฐ (0) | 2022.11.17 |
---|---|
PuppyBuddy ํ๋ก์ ํธ ํ๊ณ & ๋ถํธ์บ ํ ์๋ฃ ํ๊ธฐ (0) | 2022.10.19 |