2024.10.14 - [React] - React(리액트) 티키타카 15 ( restful, tanstack, axios)
글에서 잠깐 다루었지만도 불구하고, 은폐 엄폐된 낯설다는 의견이 어느덧 분분하여
암막커튼이 오토매틱하게 스스로 뽀인또를 드러낼 수 있도록 기본 전개를 다시 해보장.
아래 tanstack 홈페이지에 가면 실시간으로 NPM 다운로드 숫자가 표시되는데, 엄청나당.
원래는 그냥 react-query였는데, 유명해져서 @tanstack 브랜드명을 가지게 되었당.
잘 쓰고싶땅?. 잘 쓰려면 아래 3가지를 일단 브레인에 단어라도 낯설지 않게 구겨넣장.
[ Restful 단어가 브레인에 잔잔하게 파도친다면 이미 너무 훌륭하당!~~]
QueryClient -> tanstack/react-query의 본체라고나 할깡.
useQuery -> 비동기 get맵핑에 사용하는 hook (읽기 동작)
useMutation -> 비동기 post / put / delete 맵핑에 사용하는 hook (쓰기 동작)
어느 길로 가야할 지 모를 때, 그때 거꾸로 길이 날 불러준다면 기리 기리 행복할거당. 가보장.
아무이름으로나 프로젝트 폴더를 맹글고 아래 명령어들로 실습 준비를 하장.
npx create-vite@latest .
npm i tailwindcss @tailwindcss/vite
npm i @tanstack/react-query
npm i axios uuid
npm run dev
필요없는 파일과 폴더 삭제 작업은 알아서 하길 으자에 기대어 기대해 본당.
아래는 vite에 tailwindcss를 위한 설정이당. (사실 이것도 생략하고팠당)
vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
});
index.css
@import "tailwindcss";
tanstack을 시작하기 전에 한가지 더 미리 준비하도록 하장.
비동기로 가져 올 더미 데이터당. 이왕이면 더 있어보이게 json-server로 준비하장.
프로젝트 폴더(package.json 파일이 있는 폴더)에 아래 파일을 맹글장.
db.json
{
"todos": [
{
"id": "1",
"man": "경미니",
"text": "기획관리"
},
{
"id": "2",
"man": "영시니",
"text": "회원관리"
},
{
"id": "3",
"man": "선주니",
"text": "꽃길관리"
},
{
"id": "4",
"man": "E7E니",
"text": "별걸관리"
},
{
"id": "5",
"man": "지혀니",
"text": "일정관리"
},
{
"id": "6",
"man": "사나니",
"text": "조직관리"
},
{
"id": "7",
"man": "추우니",
"text": "결재관리"
},
{
"id": "8",
"man": "세이니",
"text": "보안관리"
}
]
}
db.json 파일을 데이터베이스로 json-server를 restful 서비스로 올리장.
npx json-server db.json -p 8272
브라우져 주소 표시줄에 http://localhost:8272/todos 를 입력하면 더미 데이터가 보여야 한당.
[ 브라우져 주소 표시줄에 주소를 쓰는 것 get 맵핑과 같당. 그렇당 ]
json-server로 백엔드 준비는 대략 끝나버렸고, 이젠 tanstack을 쓰기 위한 설정을 해야 한당.
아래 처럼 하장. [ main.jsx에 해도 된당. 그냥 오늘 App.jsx에 할꺼당.]
App.jsx [ 그저 사용을 위한 설정이라 받아들이장 ]
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Todos from "./Todos";
const queryClient = new QueryClient();
function App() {
return (
<>
<h1>E7E와 함께 화해의 길</h1>
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
</>
);
}
export default App;
App.jsx 소스를 보닝, Todos.jsx가 필요함이 어리둥절 보인당.
[ 쉬우면 쉬는 느낌이 난당. 먼저 쉽게 비교를 위해 useEffect를 이용하여 만들어 보장.]
Todos.jsx
import axios from "axios";
import { useEffect, useState } from "react";
function Todos() {
const [data, setData] = useState([]);
useEffect(() => {
const getData = async (url) => {
const response = await axios.get(url);
setData(response.data);
};
getData("http://localhost:8272/todos");
}, []);
return (
<>
<div>
{data.map((todo) => (
<div
key={todo.id}
className="border-2 border-solid border-pink-500 pb-4 mb-2"
>
<div className="text-2xl m-3 pl-4">
담당자:{" "}
<span className="text-violet-500 font-extrabold pl-8">
{todo.man}
</span>
</div>
<div className="text-4xl pl-6">
할 일:{" "}
<span className="text-red-900 font-extrabold">{todo.text}</span>
</div>
</div>
))}
</div>
</>
);
}
export default Todos;
실행 결과는 당연히 json-server만 잘 돌고 있다면 잘 나올꺼당.
이제 위 소스를 tanstack query를 쓰는 것으로 바꾸어 보장.
데이터를 가져오는 것이니 get맵핑, 그렇다면 useQuery 훅을 써야 한당.
아래 링크를 클릭하면 useQuery 문법이 나온당. 헉~~ 입이 쩌억 벌어진당.
https://tanstack.com/query/latest/docs/framework/react/reference/useQuery#usequery
닫자. 별거없당. 그저 장황한거당. 조금만 눈에 힘주고 보장.
브레인에는 비동기는 성공, 실패, 대기(펜딩) 3가지 상태가 있음을 으미심장하면서..
헉 비슷한 것들이 이름만 다르게 나열되었음이당.
우린 일단 3가지만 울궈 먹는 방식으로 쓰장. 기본이 이해되면 나머진 시간문제당.
data [ 성공하면 결과값이 담긴다 ]
erro [ 당연 에러가 있다면 담긴다 ]
isPending [ 아직 비동기 미션 진행 중이다]
구조 분해를 이용, 위 3가지만 꺼내 쓸거당.
const { data, error, isPending } = useQuery({ queryKey: 고유ID값, queryFn: 함수});
Todos.jsx [ useQuery를 이용한 소스]
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
function Todos() {
const { data, error, isPending } = useQuery({
queryKey: ["getTodos"], // 고유값만 주면 된당.
queryFn: async () => {
// 서버에서 값을 가져올 비동기 함수, Promise를 리턴!
const response = await axios.get("http://localhost:8272/todos");
return response.data;
},
});
console.log("디버깅", data, error, isPending);
if (isPending) return <div>아직 지둘령</div>;
if (error) return <div>헉 에러 {error.message}</div>;
// 아래는 성공했을 때... 여기서도 re-rendering은 잊지 말길
return (
<>
<div>
{data.map((todo) => (
<div
key={todo.id}
className="border-2 border-solid border-pink-500 pb-4 mb-2"
>
<div className="text-2xl m-3 pl-4">
담당자:{" "}
<span className="text-violet-500 font-extrabold pl-8">
{todo.man}
</span>
</div>
<div className="text-4xl pl-6">
할 일:{" "}
<span className="text-red-900 font-extrabold">{todo.text}</span>
</div>
</div>
))}
</div>
</>
);
}
export default Todos;
실행 결과는 당근 아니 될 이유가 없으닝, 이전과 같을지어당.
queryKey 속성과 queryFn 속성이 낯설 수 있당. 아니 낯설당.
queryKey 속성은 배열인데, 그저 구분할 수 있는 고유값만 주면 된당.
["member","getData"] 랑 ["produce","getData"]는 다르당. 오켕?
사용법을 받아들여 후다닥 익숙해지는 게 뽀인또당. 좀 느끼미가 찾아왔을깡?
그럼 이제 useMutation 훅 사용을 익히기 위해 insert기능을 넣어보장
Todos.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { useRef } from "react";
import { v4 as uuidv4 } from "uuid";
function Todos() {
const queryClient = useQueryClient();
const manRef = useRef(null);
const todoRef = useRef(null);
// useQuery
const { data, error, isPending } = useQuery({
queryKey: ["getTodos"], // 고유값만 주면 된당.
queryFn: async () => {
// 서버에서 값을 가져올 비동기 함수, Promise를 리턴!
const response = await axios.get("http://localhost:8272/todos");
return response.data;
},
});
//useMutation
const { mutate: insertMute } = useMutation({
mutationFn: async (newTodo) => {
await axios.post("http://localhost:8272/todos", newTodo);
},
onSuccess: () => {
alert("추가 성공");
queryClient.invalidateQueries(["getFriends"]); // 요거에 의해 useQuery 다시 실행!
},
});
console.log("디버깅", data, error, isPending);
const regTodo = async (e) => {
e.preventDefault();
let newTodo = {
id: uuidv4,
man: manRef.current.value,
text: todoRef.current.value,
};
console.log("항상 체킁:", newTodo);
insertMute(newTodo);
};
if (isPending) return <div>아직 지둘령</div>;
if (error) return <div>헉 에러 {error.message}</div>;
// 아래는 성공했을 때... 여기서도 re-rendering은 잊지 말길
return (
<>
<div className="border-2 border-solid border-blue-500 pb-4 text-3xl pl-50 pt-5">
<form onSubmit={regTodo}>
담당자{" "}
<input
type="text"
className="border-3 border-amber-600 rounded-2xl text-center"
ref={manRef}
defaultValue={"꾸므리"}
/>
<br />
할일{" "}
<input
type="text"
className="border-3 ml-7 m-3 border-amber-600 rounded-2xl text-center"
ref={todoRef}
defaultValue={"전부다"}
/>
<br />
<button className="border-amber-300 bg-slate-900 ml-25 p-3 text-5xl text-white w-80 border-2 rounded-3xl">
등록
</button>
</form>
</div>
<div>
{data.map((todo) => (
<div
key={todo.id}
className="border-2 border-solid border-pink-500 pb-4 mb-2"
>
<div className="text-2xl m-3 pl-4">
담당자:{" "}
<span className="text-violet-500 font-extrabold pl-8">
{todo.man}
</span>
</div>
<div className="text-4xl pl-6">
할 일:{" "}
<span className="text-red-900 font-extrabold">{todo.text}</span>
</div>
</div>
))}
</div>
</>
);
}
export default Todos;
사용자 입력을 받기 위해 form을 넣었당. (그리 중요하지는 않당.)
useMutaition 부분이 중요한딩... 아래 링크가 사용법이당.
https://tanstack.com/query/latest/docs/framework/react/reference/useMutation#usemutation
보면 역시 허억!~~ 하고 놀랄수 있당. 괜찮당.
mutate만 잘 써도 90점이당. 누니 엑스칼리버인 사람은 mutateAsynce도
시선을 사로잡고 놓지 않을 것이다.
mutate와 mutateAsync의 차이점은 mutateAsync는 return을 할 수 있고
mutate는 return을 할 수 없다는 차이당. 그렇당.
난 mutate를 좋아한당. 왱? 사실 단순히 별로 return 할 게 없어서당.
mutate 사용법은 억지로 크게 2가지 형태로 볼 수 있다.
// 사용법 1
const 원하는변수명 = useMutation({... 생략});
원하는변수명.mutate(); // mutationFn 속성에 등록된 함수 호출
// 사용법 2
const { mutate: insertMute } = useMutation({... 생략})
insertMute(); // mutationFn 속성에 등록된 함수 호출
다음으로 눈여겨 보아야 할 코드는 onSuccess 안에 들어있는 아래 코드당.
queryClient.invalidateQueries(["getFriends"]);
queryKey ["getFriends"]를 가진 query를 무효화 한다는 뜻이다.
결론은 queryKey ["getFriends"]를 가진 query를 다시 실행하란 뜻이다.
결국 insert후에 서버에 결과 리스트를 다시 요청해서 받으란 으미! (오켕?)
이제 다시 소스를 본다면, 대부분이 이해 될거당.
나의 소스 실행 결과는 아래와 같당.
이제 결과는 그렇다치고, 쪼메 있어보여야 할 시점이 되었으니, 허세 좋게
리팩토링의 세계로 들어가 보자. (본인 소스 리팩토링은 본인에게 너무도 크나 큰 능력 UP이당!!)
먼저 src 폴더 아래에 api라고 폴더를 만들고 거기에 todos.js 파일을 아래처럼 맹글장.
todos.js (보자 마자 어디에 적용할까 느끼미 친구가 바로 올꺼당)
import axios from "axios";
const todoAPI = axios.create({
baseURL: "http://localhost:8282/todos",
});
export const getTodos = async () => {
const reponse = await todoAPI.get("");
return reponse.data;
};
export const insertTodo = async (todo) => {
console.log("체킁", todo);
return await todoAPI.post("", todo);
};
아직이당. Todos.jsx 안에 요래 조래 코드가 많아져서 보기가 힘들당.
바로 이때가 커스텀 hook을 만들어야 할 때당.
src아래 hook이라 폴더를 맹글공, useTodos.js 파일을 아래 처럼 맹글장.
useTodos.js
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useRef } from "react";
import { v4 as uuidv4 } from "uuid";
import { getTodos, insertTodo } from "../api/todos";
function useTodos() {
const queryClient = useQueryClient();
const manRef = useRef(null);
const todoRef = useRef(null);
// useQuery
const { data, error, isPending } = useQuery({
queryKey: ["getTodos"], // 고유값만 주면 된당.
queryFn: getTodos,
});
//useMutation
const { mutate: insertMute } = useMutation({
mutationFn: insertTodo,
onSuccess: () => {
queryClient.invalidateQueries(["getFriends"]); // 요거에 의해 useQuery 다시 실행!
},
});
console.log("디버깅", data, error, isPending);
const regTodo = async (e) => {
e.preventDefault();
let newTodo = {
id: uuidv4,
man: manRef.current.value,
text: todoRef.current.value,
};
console.log("항상 체킁:", newTodo);
insertMute(newTodo);
};
// 여기가 중요, 밖에서 필요한 걸 빠짐없이 잘 return 해줘야 함
return { manRef, todoRef, regTodo, data, error, isPending };
}
export default useTodos;
여기가 중요라고 달린 주석부분이 커스텀 훅을 만들때 너무도 소중하당.
이제 커스텀 hook useTodos 덕분에 Todos.jsx는 아래 처럼 간단해 진당.
Todos.jsx
import useTodos from "./hook/useTodos";
function Todos() {
const { manRef, todoRef, regTodo, data, error, isPending } = useTodos();
if (isPending) return <div>아직 지둘령</div>;
if (error) return <div>헉 에러 {error.message}</div>;
// 아래는 성공했을 때... 여기서도 re-rendering은 잊지 말길
return (
<>
<div className="border-2 border-solid border-blue-500 pb-4 text-3xl pl-50 pt-5">
<form onSubmit={regTodo}>
담당자{" "}
<input
type="text"
className="border-3 border-amber-600 rounded-2xl text-center"
ref={manRef}
defaultValue={"꾸므리"}
/>
<br />
할일{" "}
<input
type="text"
className="border-3 ml-7 m-3 border-amber-600 rounded-2xl text-center"
ref={todoRef}
defaultValue={"전부다"}
/>
<br />
<button className="border-amber-300 bg-slate-900 ml-25 p-3 text-5xl text-white w-80 border-2 rounded-3xl">
등록
</button>
</form>
</div>
<div>
{data.map((todo) => (
<div
key={todo.id}
className="border-2 border-solid border-pink-500 pb-4 mb-2"
>
<div className="text-2xl m-3 pl-4">
담당자:{" "}
<span className="text-violet-500 font-extrabold pl-8">
{todo.man}
</span>
</div>
<div className="text-4xl pl-6">
할 일:{" "}
<span className="text-red-900 font-extrabold">{todo.text}</span>
</div>
</div>
))}
</div>
</>
);
}
export default Todos;
결과는 말할 필요도 없이 이전과 동일 할 것이다.
그저 누군가에게는 찔끔이나마 도우미가 되었으면 하는 얄팍하게 찔끔하는 맴이당.
오해로 얼룩진 땅
착각으로 뒤틀어진 하늘
착각을 투영하는 의혹의 바다
거기에서 시작된 의심의 바람
어디에도 진실은 없다는 진실
그럼에도 철철 내리는 비는
그 모두를 섞고 비벼서
의심의 바람속에 두둥실 떠도는
미세 의혹에 대한 의문까지도
끝끝내 정화하지 못한다.
그치만 어디에도 정화된 곳은 있고,
그치만 어디에도 오염된 곳도 있다.
그 어디에서도 사랑과미움은 함께당.~~
https://www.youtube.com/watch?v=G6Afw4ni6tQ
React(리액트) 티키타카 33 (wx-react-gantt) SVAR Gantt (3) | 2025.03.27 |
---|---|
React(리액트) 티키타카 32 (motion) 애니메이션 (0) | 2025.03.26 |
React(리액트) 티키타카 31 (Recharts) (0) | 2025.03.25 |
React(리액트) 티키타카 30 (SweetAlert2) (0) | 2025.03.24 |
React(리액트) 티키타카 29 (CkEditor) (0) | 2025.03.22 |