
Fetch ๋น๋๊ธฐ ํต์ ๊ณผ Tanstack Query v5 ์์ ์ ๋ณต โ๏ธ
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด, ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋น๋๊ธฐ ํต์ ์ ํผํ ์ ์๋ ๊ณผ์ ์
๋๋ค. ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ณ , ํผ์ ์ ์ถํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์ ์ ์ฅํ๋ฉฐ, ํ์ด์ง๋ฅผ ์ด๋ํ๋ฉด ๋ค์ ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ผ ํ๋ ์ผ์ด ๋ฐ๋ณต๋๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด๋ ์๋ฐ์คํฌ๋ฆฝํธ๋ fetch() API๋ฅผ ํตํด ๋น๋๊ธฐ HTTP ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ , ์ด๋ฅผ ํตํด ํ๋ก ํธ์๋์ ๋ฐฑ์๋๊ฐ ์ํตํ๊ฒ ๋ฉ๋๋ค.
ํ์ง๋ง ์ค์ ์๋น์ค์์๋ ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ ์ด์์ด ํ์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์ด๋ฐ ์ํฉ๋ค์ ์๊ฐํด ๋ณผ ์ ์์ต๋๋ค.
์ ๋ฌธ์ ๋ค์ Fetch๋ง์ผ๋ก๋ ํด๊ฒฐํ๊ธฐ ์ด๋ ต์ต๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ TanStack Query(๊ตฌ React Query)๊ฐ ๋ฑ์ฅํ๊ฒ ๋์์ต๋๋ค.
TanStack Query๋ ๋จ์ํ ๋ฐ์ดํฐ ์์ฒญ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋๋ผ, ์๋ฒ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋ฒ์ ๊ด๋ จ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ , ์บ์ฑํ๊ณ , ๋๊ธฐํํ๊ณ , ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ ๋ฐ์ดํธํ๋ฉฐ, ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฑ ๋ณต์กํ ๋ก์ง์ ๊ฐ๋จํ๊ฒ ๊ด๋ฆฌํ ์ ์๋๋ก ๋์์ค๋๋ค.
Promise๋ ์๋ฐ์คํฌ๋ฆฝํธ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ์ฌ์ฉ๋๋ ๊ฐ์ฒด์ ๋๋ค. ๊ฐ์ฒด๋ ๋ฐ์ดํฐ ๋ฌถ์์ด์ฃ . ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ ๋ฌถ์์ด Promise์ ๋๋ค.
๋น๋๊ธฐ ์ฒ๋ฆฌ๋, 'ํน์ ์ฝ๋์ ์คํ์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ , ๋ค์ ์ฝ๋๋ฅผ ๋จผ์ ์ํํ๋ ์๋ฐ์คํฌ๋ฆฝํธ์ ํน์ฑ'์ ์๋ฏธํฉ๋๋ค. ๋ญ๊ฐ์ด์ด์ ์์ดํ๋ผ์ด์ด๋ก 15๋ถ ๋์ ๊ตฝ๋๋ค๊ณ ํ๋ฉด, ์์ดํ๋ผ์ด์ด ์กฐ๋ฆฌ๊ฐ ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๊ทธ ์๊ฐ ๋์ ํ์คํ๋ฅผ ๋ง๋ค ์ ์๊ฒ ์ฃ . '์์ดํ๋ผ์ด์ด ์กฐ๋ฆฌ๊ฐ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ํ์คํ๋ฅผ ๋ง๋๋ ๊ฒ'์ด ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋๋ค.
Promise๋ ์ฃผ๋ก ์๋ฒ์์ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ํ๋ฉด์ ํ์ํ ๋ ์ฌ์ฉํฉ๋๋ค.
$.get('url ์ฃผ์/products/1', function(response) {
// ...
});
์ API๊ฐ ์คํ๋๋ฉด, ์๋ฒ๋ก "๋ฐ์ดํฐ๋ฅผ ํ๋ ๋ณด๋ด์ฃผ์ธ์."๋ผ๋ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ธฐ๋ ์ ์, ๋ง์น ๋ฐ์ดํฐ๋ฅผ ๋ค ๋ฐ์์จ ๊ฒ์ฒ๋ผ ํ๋ฉด์ ๋ฐ์ดํฐ๋ฅผ ํ์ํ๋ ค๊ณ ํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฑฐ๋ ๋น ํ๋ฉด์ด ๋์ค๊ฒ ์ฃ . ์ด์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ฐฉ๋ฒ ์ค ํ๋๋ก Promise๋ฅผ ํ์ฉํ๊ฒ ๋ฉ๋๋ค.
Promise๋ ์ธ ๊ฐ์ง ์ํ(states)๋ฅผ ๊ฐ์ต๋๋ค.
new Promise() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ์๊ฐ Pending ์ํ๊ฐ ๋ฉ๋๋ค.
new Promise();
new Promise() ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ ์ธํ ์ ์๋๋ฐ์, ์ฝ๋ฐฑ ํจ์์ ์ธ์๋ resolve์ reject์ ๋๋ค.
new Promise(function(resolve, reject) {
// ...
});
์ฝ๋ฐฑ ํจ์์ ์ธ์ resolve๋ฅผ ์๋์ ๊ฐ์ด ์คํํ๋ฉด Fulfilled ์ํ๊ฐ ๋์ฃ .
new Promise(function(resolve, reject) {
resolve();
});
Fulfilled ์ํ๊ฐ ๋๋ฉด Promise๋ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํํฉ๋๋ค. ํด๋น ๋ฐํ๊ฐ์ ๋ํด์ then() ๋ฉ์๋๋ฅผ ์ ์ฉํ์ฌ ๋ฐํ๊ฐ์ ๋ฐ์ ์ ์์ต๋๋ค.
function getData() {
return new Promise(function(resolve, reject) {
var data = 100;
resolve(data);
});
}
// resolve()์ ๊ฒฐ๊ณผ ๊ฐ data๋ฅผ resolvedData๋ก ๋ฐ์
getData().then(function(resolvedData) {
console.log(resolvedData); // 100
});
new Promise()๋ฅผ ์์ฑํ ๋ ์ ์ธํ ์ฝ๋ฐฑ ํจ์์ ์ธ์ ์ค ํ๋์ธ reject๋ฅผ ํธ์ถํ๋ฉด Rejected ์ํ๊ฐ ๋ฉ๋๋ค.
new Promise(function(resolve, reject) {
reject();
});
Rejected ์ํ๊ฐ ๋๋ฉด, ์คํจ์ ์ด์ ๋ฅผ catch() ๋ฉ์๋๋ก ๋ฐ์ ์ ์์ต๋๋ค.
function getData() {
return new Promise(function(resolve, reject) {
reject(new Error("Request is failed"));
});
}
// reject()์ ๊ฒฐ๊ณผ ๊ฐ Error๋ฅผ err์ ๋ฐ์
getData().then().catch(function(err) {
console.log(err); // Error: Request is failed
});
async / await๋ ์์์ ์ค๋ช ํ Promise๋ฅผ ๋ ํธํ๊ฒ ์ฌ์ฉํ๊ธฐ ์ํ ์๋ฐ์คํฌ๋ฆฝํธ ๋ฌธ๋ฒ์ ๋๋ค.
async ํค์๋๋ function ์์ ์์นํฉ๋๋ค.
async function f() {
return 1;
}
function ์์ async๋ฅผ ๋ถ์ด๋ฉด, ํด๋น ํจ์๋ ํญ์ Promise๋ฅผ ๋ฐํํฉ๋๋ค. Promise๊ฐ ์๋ ๊ฐ์ ๋ฐํํ๋๋ผ๋, Fulfilled ์ํ์ Promise๋ก ๊ฐ์ธ '์ดํ๋ Promise'๊ฐ ๋ฐํ๋๋๋ก ํฉ๋๋ค.
async function f() {
return 1;
}
f().then(alert); // 1
๋ช ์์ ์ผ๋ก Promise๋ฅผ ๋ฐํํ๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ฐ, ๊ฒฐ๊ณผ๋ ๋์ผํฉ๋๋ค.
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
await๋ async ํจ์ ์์์๋ง ๋์ํ๋ ํค์๋์ ๋๋ค.
์๋ฐ์คํฌ๋ฆฝํธ๋ await ํค์๋๋ฅผ ๋ง๋๋ฉด, Promise๊ฐ ์ฒ๋ฆฌ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. ๊ฒฐ๊ณผ๋ ๊ทธ ์ดํ์ ๋ฐํ๋๋๋ฐ์. 1์ด ํ ์ดํ๋๋ Promise ์์๋ฅผ ํตํด awiat์ ๋์์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("์๋ฃ!"), 1000)
});
let result = await promise; // ํ๋ผ๋ฏธ์ค๊ฐ ์ดํ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆผ (*)
alert(result); // "์๋ฃ!"
}
f();
ํจ์๋ฅผ ํธ์ถํ๊ณ , ํจ์ ๋ณธ๋ฌธ์ด ์คํ๋๋ ๋์ค์ (*) ๋ถ๋ถ์์ ์คํ์ด ์ ์ '์ค๋จ'๋์๋ค๊ฐ Promise๊ฐ ์ฒ๋ฆฌ๋๋ฉด ์คํ์ด ์ฌ๊ฐ๋ฉ๋๋ค. await๋ ๋ง ๊ทธ๋๋ก Promise๊ฐ ์ฒ๋ฆฌ๋ ๋๊น์ง ํจ์ ์คํ์ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋ง๋ญ๋๋ค. Promise๊ฐ ์ฒ๋ฆฌ๋๊ธธ ๊ธฐ๋ค๋ฆฌ๋ ๋์์, ์์ง์ด ๋ค๋ฅธ ์ผ์ ํ ์ ์๊ธฐ ๋๋ฌธ์ CPU ๋ฆฌ์์ค๊ฐ ๋ญ๋น๋์ง ์์ต๋๋ค. Promise.then()๋ณด๋ค ๊ฐ๋
์ฑ์ด ์ข๊ณ ์ฐ๊ธฐ๋ ์ฝ๋ค๋ ์ฅ์ ๋ํ ์์ต๋๋ค.
โ ๏ธ Promise์ async / await ์์ฝ
Promise๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ์ฌ์ฉ๋๋ ๊ฐ์ฒด๋ก, ์์ ์ ์ํ(๋๊ธฐ, ์ดํ, ์คํจ)๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. ์ด๋ฅผ ๋ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ ์ ์๋๋ก async/await ๋ฌธ๋ฒ์ด ๋์ ๋์์ผ๋ฉฐ, ์ด๋ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ๋ง์น ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์ฝ๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด์ค๋๋ค.
Fetch API๋ HTTP Request๋ฅผ ๋ณด๋ด๊ณ ๊ทธ์ ๋ํ Response๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ JavaScript ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
fetch() ํจ์๋ฅผ ํธ์ถํ์ฌ Request๋ฅผ ๋ณด๋ ๋๋ค. ์์ฒญํ URL์ ๋ด์ ๋ฌธ์์ด์ด๋ Request ๊ฐ์ฒด๋ฅผ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๋๊ธฐ๊ณ , ์ ํ์ ์ผ๋ก Request๋ฅผ ๊ตฌ์ฑํ๋ ์ต์ ๊ฐ์ฒด๋ฅผ ๋ ๋ฒ์งธ ์ธ์๋ก ๋๊ธธ ์ ์์ต๋๋ค.
fetch() ํจ์๋ Promise๋ฅผ ๋ฐํํฉ๋๋ค. ์ดํ Response ๊ฐ์ฒด์ ๋ํด ์ ์ ํ ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ Request์ ์ํ๋ฅผ ํ์ธํ๊ณ , Response ๋ณธ๋ฌธ์ ํ ์คํธ ๋๋ JSON๊ณผ ๊ฐ์ ๋ค์ํ ํ์์ผ๋ก ์ถ์ถํ ์ ์์ต๋๋ค.
async function getData() {
const url = "https://example.org/products.json";
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const json = await response.json();
console.log(json);
} catch (error) {
console.error(error.message);
}
}
json() ๋ฉ์๋ ๐ฃjson() ๋ฉ์๋๋ ์๋ต(Response) ๋ฐ์ดํฐ๋ฅผ JSON ๋ฌธ์์ด์์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ํ์ฑ(parsing) ํด์ฃผ๋ ๋น๋๊ธฐ ๋ฉ์๋์ ๋๋ค. ์ฆ, ์๋ฒ๋ก๋ถํฐ ๋ฐ์ ๋ฌธ์์ด ํํ์ JSON ๋ฐ์ดํฐ๋ฅผ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ๋ณํํด ์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
Fetch API๋ก ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ฉด, ์๋ฒ๋ ์ผ๋ฐ์ ์ผ๋ก ๋ฌธ์์ด ํํ์ JSON์ ์๋ต ๋ณธ๋ฌธ์ ๋ด์ ๋ณด๋ ๋๋ค. ์ด๋ HTTP ํต์ ์ด ๋ณธ์ง์ ์ผ๋ก ํ ์คํธ(ํน์ ๋ฐ์ด๋๋ฆฌ) ๊ธฐ๋ฐ์ ๋ฐ์ดํธ ์คํธ๋ฆผ๋ง ์ ๋ฌํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
ํ์ง๋ง ์ด ๋ฌธ์์ด์ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋์์ ์ง์ ๋ค๋ฃจ๊ธฐ๋ ๋ถํธํ๋ฏ๋ก, ์ด๋ฅผ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ๋ณํํด์ผ ํ๋ฉฐ, ์ด๋ Response ๊ฐ์ฒด์ json() ๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค.
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => response.json()) // ์๋ต์ JSON ๋ฌธ์์ด โ JS ๊ฐ์ฒด๋ก ๋ณํ
.then(data => {
console.log(data); // ๋ณํ๋ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด ์ถ๋ ฅ
console.log(data.name); // ๊ฐ์ฒด์ ํน์ ์์ฑ ์ ๊ทผ
});
์ ์ฝ๋์ ๋ํ ์คํ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{
id: 1,
name: "Leanne Graham",
username: "Bret",
email: "Sincere@april.biz",
...
}
Leanne Graham
JSON.stringify()๋ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์์ด๋ก ๋ณํํ๋ ํจ์์ ๋๋ค. ์ฆ, ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ฅผ ์๋ฒ์ ์ ์กํ๊ธฐ ์ ํฉํ ๋ฌธ์์ด ํํ๋ก ๋ฐ๊ฟ์ฃผ๋ ๋๊ตฌ์ ๋๋ค.
fetch API๋ฅผ ์ฌ์ฉํด ํด๋ผ์ด์ธํธ์์ ์๋ฒ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๋, HTTP ํต์ ์ ํ ์คํธ(๋๋ ๋ฐ์ด๋๋ฆฌ) ๊ธฐ๋ฐ์ ๋ฐ์ดํธ ์คํธ๋ฆผ๋ง ์ ์กํ ์ ์์ต๋๋ค.
๋ฐ๋ผ์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ณด๋ผ ์๋ ์๊ณ , ์ด๋ฅผ JSON.stringify()๋ฅผ ์ฌ์ฉํด JSON ๋ฌธ์์ด๋ก ๋ณํํ ํ ์ ์กํด์ผ ํฉ๋๋ค.
const user = {
name: 'Alice',
age: 25
};
const jsonString = JSON.stringify(user); // ๊ฐ์ฒด โ JSON ๋ฌธ์์ด ๋ณํ
console.log(jsonString);
// ์๋ฒ๋ก ์ ์ก (์์์ฉ์ผ๋ก ์ค์ ์ ์ก๋์ง ์์)
fetch('https://example.com/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // JSON ํ์์์ ์๋ฒ์ ์๋ฆผ
},
body: jsonString // ๋ฌธ์์ด๋ก ๋ณํํ ๋ฐ์ดํฐ๋ฅผ ์ ์ก
});
jsonString์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{"name":"Alice","age":25}
โ ๏ธ Fetch API ์ฌ์ฉํ๊ธฐ ์์ฝ
Fetch API๋ ์๋ฒ์์ ํต์ ์ ์ํ ๋ด์ฅ ํจ์๋ก, ์๋ต์ ์ฒ๋ฆฌํ ๋๋ json() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด JSON ๋ฌธ์์ด์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ๋ณํํฉ๋๋ค. ๋ฐ๋๋ก ์๋ฒ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๋๋ JSON.stringify()๋ฅผ ์ฌ์ฉํด ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํฉ๋๋ค.
try...catch๋ ์ฝ๋ ์คํ ์ค ์๋ฌ๊ฐ ๋ฐ์ํด๋ ํ๋ก๊ทธ๋จ์ด ๋ฉ์ถ์ง ์๋๋ก ์ฒ๋ฆฌํ๋ ๋ฌธ๋ฒ์ ๋๋ค. ์ฆ, ์๋ฌ๋ฅผ ์ก์์ ์ํ๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ํด์ฃผ๋ ๋๊ตฌ์ ๋๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
try {
// ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์๋ ์ฝ๋
} catch (error) {
// ์์ธ๊ฐ ๋ฐ์ํ์ ๋ ์คํ๋๋ ์ฝ๋
} finally {
// ์์ธ ๋ฐ์ ์ฌ๋ถ์ ๊ด๊ณ์์ด ํญ์ ์คํ๋๋ ์ฝ๋
}
์ ์ ์์ฑํ๋ ์ฝ๋์ ๋๋ค.
const user = {
name: 'Alice',
age: 25
};
const jsonString = JSON.stringify(user); // ๊ฐ์ฒด โ JSON ๋ฌธ์์ด ๋ณํ
console.log(jsonString);
// ์๋ฒ๋ก ์ ์ก (์์์ฉ์ผ๋ก ์ค์ ์ ์ก๋์ง ์์)
fetch('https://example.com/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // JSON ํ์์์ ์๋ฒ์ ์๋ฆผ
},
body: jsonString // ๋ฌธ์์ด๋ก ๋ณํํ ๋ฐ์ดํฐ๋ฅผ ์ ์ก
});
์ด์ ์ ์ฝ๋์ try..catch๋ฅผ ์ ์ฉํด ๋ณด๊ฒ ์ต๋๋ค.
const user = {
name: 'Alice',
age: 25
};
async function sendUserData() {
try {
const jsonString = JSON.stringify(user); // ๊ฐ์ฒด โ JSON ๋ฌธ์์ด ๋ณํ
console.log(jsonString);
const response = await fetch('https://example.com/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // JSON ํ์์์ ์๋ฒ์ ์๋ฆผ
},
body: jsonString // ๋ฌธ์์ด๋ก ๋ณํํ ๋ฐ์ดํฐ๋ฅผ ์ ์ก
});
if (!response.ok) {
throw new Error(`์๋ฒ ์๋ต ์ค๋ฅ: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log('์๋ฒ ์๋ต ๋ฐ์ดํฐ:', data);
} catch (error) {
console.error('๋ฐ์ดํฐ ์ ์ก ์ค ์ค๋ฅ ๋ฐ์:', error);
}
}
sendUserData();
๋ฐ์ดํฐ ์ ์ก ์ค ๋ฐ์ ๊ฐ๋ฅํ ์ค๋ฅ๋ฅผ catch๋ก ์ฒ๋ฆฌํ๋ ๋ชจ์ต์ ํ์ธํ ์ ์์ต๋๋ค.
โ ๏ธ ์ค๋ฅ ์ฒ๋ฆฌ: tryโฆcatch ์์ฝ
๋น๋๊ธฐ ํต์ ๊ณผ์ ์์ ๋ฐ์ํ ์ ์๋ ์ค๋ฅ๋ try...catch ๊ตฌ๋ฌธ์ ํตํด ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์ธ ์ํฉ์์๋ ์ค๋จ๋์ง ์๊ณ ์ ์ ํ ๋์ํ ์ ์์ต๋๋ค.
Promise์ async/await, Fetch API, ๊ทธ๋ฆฌ๊ณ ์๋ฌ ์ฒ๋ฆฌ์ ๋ํด ์ดํด๋ดค์ต๋๋ค.
ํ์ง๋ง fetch๋ ๋จ์ํ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๊ณ , ์๋ต์ ๋ฐ๋ ์ญํ ๋ง ํ๋ API์ ๋๋ค. ๋ฐ๋ฉด์ TanStack Query๋ ํํ ์๋ฒ ์ํ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํด ์ฃผ๋ ๋๊ตฌ๋ผ๊ณ ๋ถ๋ฆฌ์ฃ . ๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ๋งํ๋ โ์๋ฒ ์ํ ๊ด๋ฆฌ(Server State Management)โ๊ฐ ์ ํํ ๋ฌด์์ ์๋ฏธํ๋์ง๋ถํฐ ์ง๊ณ ๋์ด๊ฐ ํ์๊ฐ ์์ต๋๋ค. ์ ๋ ์ ์๊ฐ ๋ช ํํ์ง ์์ผ๋ฉด ํ์ ์์ ์ ์ ๋ชปํ๋ ํธ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
์๋ฒ ์ํ ๊ด๋ฆฌ๋, ์๋ฒ์์ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ๋ด์์ ํจ์จ์ ์ผ๋ก ์ ์ฅํ๊ณ , ์ ๋ฐ์ดํธํ๋ฉฐ, ์ฌ๋ฌ ํ๋ฉด์ด๋ ์ปดํฌ๋ํธ์์ ์ผ๊ด๋ ์ต์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ๊ด๋ฆฌํ๋ ์์ ์ ๋งํฉ๋๋ค. fetch๋ axios๋ ๋จ์ง ์์ฒญ๊ณผ ์๋ต์ ๊ธฐ๋ฅ๋ง ์ ๊ณตํ ๋ฟ์ด์ง๋ง, ์๋ฒ ์ํ ๊ด๋ฆฌ๋ ์ด ๋ฐ์ดํฐ๋ฅผ ์ธ์ ๋ค์ ๋ถ๋ฌ์์ผ ํ ์ง, ๋ก๋ฉ์ด๋ ์๋ฌ ์ํ๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง, ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ์ด๋ป๊ฒ ์ผ๊ด๋๊ฒ ๊ณต์ ํ ์ง๊น์ง ํฌํจํฉ๋๋ค.
์ฆ, ์๋ฒ ์ํ ๊ด๋ฆฌ๋ ์๋ฒ๋ก๋ถํฐ ๋ฐ์ ๋ฐ์ดํฐ์ ๊ดํ ์์ฉ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค. TanStack Query๋ ์ด ๋ณต์กํ ๊ณผ์ ์ ๋์ ์ฒ๋ฆฌํด ์ฃผ๊ธฐ ์ํด ์ด๋ฏธ ๋ค์ํ ๊ธฐ๋ฅ์ ๊ฐ์ถ ๋๊ตฌ์
๋๋ค.
๋ฆฌ๋ ์ค๋ฅผ ๊ฐ๋ฐํ ๋ฆฌ๋์ค ํ ๋ฐ์ฆ ํ๋์ "๋์ ํ๋ก๊ทธ๋๋จธ๋ ์ฝ๋์ ์ง์ฐฉํ๊ณ , ํ๋ฅญํ ํ๋ก๊ทธ๋๋จธ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ๊ทธ ๊ด๊ณ์ ์ง์คํ๋ค."๋ผ๊ณ ๋ง์ํ์ จ์ฃ . ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ์ฒ๋ฆฌํ ๊ฒ์ธ๋ฐ, ์ฐ์ ๋ฐ์ดํฐ ์์ฒด์ ์ํ์ ๋ํ ๊ณต๋ถ๊ฐ ํ์ํ๋ค๋ ์๊ฐ์ด ๋ค์๋๋ฌ์ฃ . ๊ทธ ์์์ด staleTime๊ณผ cacheTime์ ๋๋ค.
staleTime์ useQuery ํ ์์ ์ฌ์ฉํ๋ ์์ฑ์ ๋๋ค. useQuery ํ ์ ๋ค์์ ์์ธํ ๋ค๋ฃฐ ์์ ์ ๋๋ค.
staleTime์ ๋ฐ์ดํฐ๊ฐ fresh ํ๋ค๊ณ ๊ฐ์ฃผ๋๋ ์๊ฐ์ ์๋ฏธํฉ๋๋ค. ์ฐ๋ฆฌ๊ฐ ์๊ณ ์๋ fresh๊ฐ ๋ง์ต๋๋ค. ๋ฐ์ดํฐ๊ฐ fresh ํ ์ํ์ผ ๋๋, ์ฟผ๋ฆฌ๊ฐ ๋ค์ ๋ง์ดํธ ๋๋๋ผ๋ ๋คํธ์ํฌ ์์ฒญ์ ํธ๋ฆฌ๊ฑฐ ํ์ง ์์ต๋๋ค. ์์ง ์ ๋ชจ๋ฅด๊ฒ ๋๋ฐ์. GPTํํ ๋ฌผ์ด๋ณด๋, ์ฟผ๋ฆฌ๋ ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์์ฒญ(fetch)์ ์ถ์ํํ ๋จ์๋ผ๊ณ ํฉ๋๋ค. ์ฝ๊ฒ ์ค๋ช ํด๋ฌ๋ผ๊ณ ํ๋๋ฐ ๋ต๋ณ์ ๋ณด๋ ์ธ์ด์ฝํจ์ค์์ด ํ๋ฆผ์๋ค๋ ์ฌ์ค์ ์ ์ ์์ต๋๋ค.
ํธ์์ ์ง์(aka. ํธ๋์ด)๊ฐ ๋์๋ค๊ณ ๊ฐ์ ํด ๋ณด์ฃ . ํธ๋์ด ์๋ฏผ๊ด์จ๋ ์์ธ์ฐ์ ์ ์ ํต๊ธฐํ์ ํ์ธํ๋ผ๋ ์ ์ฅ๋์ ์ ๋ฌด ์ง์๋ฅผ ๋ฐ์์ต๋๋ค. ์ฌ๊ธฐ์ ์ ํต๊ธฐํ์ด 8์ผ์ด๋ผ๋ฉด, 5์ผ์ฐจ ๊น์ง๋ ์ ์ ํ ์ํ๋ผ๊ณ ์๊ฐํด ๋ด ์๋ค. ์ฌ๊ธฐ์์ 5์ผ์ด staleTime์ ๋๋ค.
const { data } = useQuery({
queryKey: ['products'],
queryFn: fetchMilk,
staleTime: 1000 * 60 * 60 * 24 * 5, // 5์ผ (ms ๊ธฐ์ค)
});
cacheTime์ v5์์ gcTime์ผ๋ก ๋ช ์นญ์ด ๋ณ๊ฒฝ๋์์ต๋๋ค. ์ค๋ช ์ ํ ๋์๋ cacheTime์ด๋ผ๋ ๋ช ์นญ์ ์ฌ์ฉํ๊ฒ ์ต๋๋ค.
cacheTime์ ์ ํต๊ธฐํ 8์ผ์ ์๋ฏธํฉ๋๋ค. ์ฆ, 8์ผ์ด ์ง๋๋ฉด ๊ฐ๋น์ง ์ปฌ๋ ํฐ์ ์ํด ์บ์์์ ๋ฐ์ดํฐ๊ฐ ์ ๊ฑฐ๋ฉ๋๋ค.
const { data } = useQuery({
queryKey: ['products'],
queryFn: fetchMilk,
staleTime: 1000 * 60 * 60 * 24 * 5, // 5์ผ (ms ๊ธฐ์ค)
gcTime: 1000 * 60 * 60 * 24 * 8, // 8์ผ (ms ๊ธฐ์ค)
});
8์ผ์ด ์ง๋๋ฉด ํธ๋์ด ์๋ฏผ๊ด์จ๋ ์์ธ์ฐ์ ๋ฅผ ํ๊ธฐ ์ํ์ผ๋ก ๋ฑ๋กํด์ผ ํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ 3์ผ์ด ๊ถ๊ธํฉ๋๋ค.
์ ์ ํ์ง ์์ ์ฐ์ ์ธ๋ฐ(=5์ผ์ด ๊ฒฝ๊ณผ๋ ์ฐ์ ) ํ๊ธฐ ์ํ์ด ๋์ง๋ ์์(=8์ผ์ด ๊ฒฝ๊ณผ๋์ง ์์) ์ฐ์ ๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌ๋ ๊น์?
์ฐ์ stale ํ, ์ฆ ์ ์ ํ์ง ์์ ์ฐ์ ์ด๊ธฐ ๋๋ฌธ์ ์ฟผ๋ฆฌ๊ฐ ๋ง์ดํธ ๋๋ฉด ๋คํธ์ํฌ ์์ฒญ์ ํธ๋ฆฌ๊ฑฐ ํ๊ฒ ๋ฉ๋๋ค. ๋จ, 3์ผ ๋์ ๋ณ๋์ ์์ฒญ์ด ์๋ค๋ฉด 8์ผ ๊ฒฝ๊ณผ ํ์ ์บ์๊ฐ ์์ ํ ์ญ์ ๋ฉ๋๋ค.
โ ๏ธ staleTime / cacheTime ์์ฝ
staleTime์ ๋ฐ์ดํฐ๊ฐ '์ ์ 'ํ๋ค๊ณ ๊ฐ์ฃผ๋๋ ์๊ฐ์ผ๋ก, ์ด ์๊ฐ ๋์์ ์ฌ์์ฒญ์ด ๋ฐ์ํ์ง ์์ต๋๋ค. cacheTime(v5์์๋ gcTime)์ ์ฌ์ฉํ์ง ์๋ ๋ฐ์ดํฐ๊ฐ ์บ์์ ๋จ์์๋ ์๊ฐ์ผ๋ก, ์ด ์๊ฐ์ด ์ง๋๋ฉด ๋ฉ๋ชจ๋ฆฌ์์ ์ ๊ฑฐ๋ฉ๋๋ค. ๋ ์ค์ ์ ์ ์ ํ ์กฐํฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ์ต์ ํํ ์ ์์ต๋๋ค.
useQuery()๋, ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋ ์ฌ์ฉํฉ๋๋ค. ํ์ ํ๊ฒ ์ง๋ง, ์กฐํ๊ฐ ์๋ ๋ณ๊ฒฝ ์์ ์ ํ ๋์๋ useMutation()์ ์ฌ์ฉํฉ๋๋ค.
queryKey๋ useQuery์์ ๊ฐ์ฅ ์ค์ํ ๋งค๊ฐ๋ณ์ ์ค ํ๋๋ก, ์ฟผ๋ฆฌ๋ฅผ ๊ณ ์ ํ๊ฒ ์๋ณํ๋ ์ญํ ์ ํฉ๋๋ค. TanStack Query๋ ์ด ํค๋ฅผ ์ฌ์ฉํ์ฌ ๋ด๋ถ์ ์ผ๋ก ์บ์ฑ, ์ค๋ณต ์ ๊ฑฐ, ์ ๋ฐ์ดํธ ๋ฑ์ ๊ด๋ฆฌํฉ๋๋ค.
queryKey๋ ๋ฌธ์์ด, ์ซ์, ๋๋ ๊ฐ์ฒด๋ฅผ ํฌํจํ '๋ฐฐ์ด ํํ'๋ก ์ ์ํฉ๋๋ค.
// ๊ธฐ๋ณธ ๋ฌธ์์ด ํค
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos
});
๋์ ํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋๋ ํด๋น ๊ฐ์ queryKey์ ํฌํจ์ํต๋๋ค.
// ํน์ ํ ์ผ ํญ๋ชฉ ๊ฐ์ ธ์ค๊ธฐ
const { data: todoItem } = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetchTodoById(todoId)
});
์ด๋ ๊ฒ ๊ตฌ์ฑํ๋ฉด todoId๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ๋ค์ ์คํ๋ฉ๋๋ค.
ํํฐ๋ ์ ๋ ฌ ์กฐ๊ฑด๊ณผ ๊ฐ์ ๋ณต์กํ ๋งค๊ฐ๋ณ์๋ ๊ฐ์ฒด ํํ๋ก ํฌํจ์ํต๋๋ค.
// ์ํ์ ์ฐ์ ์์๋ก ํํฐ๋ง๋ ํ ์ผ ๋ชฉ๋ก
const { data: filteredTodos } = useQuery({
queryKey: ['todos', { status, priority, page }],
queryFn: () => fetchTodosByFilter({ status, priority, page })
});
queryKey๋ ๋จ์ํ ์๋ณ์ ์ด์์ ์ญํ ์ ํฉ๋๋ค.
// 'todos'๋ก ์์ํ๋ ๋ชจ๋ ์ฟผ๋ฆฌ ๋ฌดํจํ
queryClient.invalidateQueries({ queryKey: ['todos'] });
// ํน์ ํ ์ผ ํญ๋ชฉ ๊ด๋ จ ์ฟผ๋ฆฌ๋ง ๋ฌดํจํ
queryClient.invalidateQueries({ queryKey: ['todo', todoId] });
ํจ์จ์ ์ธ queryKey ๊ตฌ์กฐํ๋ฅผ ์ํ ๋ช ๊ฐ์ง ์ ๋ต์ด ์์ต๋๋ค.
['users'] // ์ฌ์ฉ์ ๋ชฉ๋ก
['users', userId] // ํน์ ์ฌ์ฉ์
['users', userId, 'projects'] // ํน์ ์ฌ์ฉ์์ ํ๋ก์ ํธ
['users', userId, 'projects', projectId] // ํน์ ์ฌ์ฉ์์ ํน์ ํ๋ก์ ํธ
['posts', { filters: { status, category }, sort, page }]
์ด๋ฌํ ์ ๋ต์ ์ฟผ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๋ ์ฝ๊ฒ ๋ง๋ค๊ณ , ๊ด๋ จ ์ฟผ๋ฆฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๋ฌดํจํํ ์ ์๊ฒ ํด์ค๋๋ค.
queryFn์ useQuery์ ๋ ๋ค๋ฅธ ํ์ ๋งค๊ฐ๋ณ์๋ก, ์ค์ ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋น๋๊ธฐ ํจ์์ ๋๋ค. ์ด ํจ์๋ Promise๋ฅผ ๋ฐํํด์ผ ํ๋ฉฐ, ์๋ฒ๋ ๋ค๋ฅธ ๋ฐ์ดํฐ ์์ค์์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ์ญํ ์ ํฉ๋๋ค.
queryFn์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ๋ Promise๋ฅผ ๋ฐํํ๋ ํจ์์ ๋๋ค.
const { data } = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const response = await fetch('https://api.example.com/todos');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
});
axios, fetch ๋ฑ ์ํ๋ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
TanStack Query v5์์ queryFn์ ์ ์ฉํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ํฌํจํ ๊ฐ์ฒด๋ฅผ ์๋์ผ๋ก ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ต๋๋ค.
const { data } = useQuery({
queryKey: ['todos', { status, page }],
queryFn: async ({ queryKey, signal, meta }) => {
// queryKey์์ ํ์ํ ์ ๋ณด ์ถ์ถ
const [_key, { status, page }] = queryKey;
// ์์ฒญ ์ทจ์๋ฅผ ์ํ AbortSignal ์ฌ์ฉ
const response = await fetch(
`https://api.example.com/todos?status=${status}&page=${page}`,
{ signal }
);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
meta: { authRequired: true }
});
queryFn์์ ๋ฐ์ํ ์ค๋ฅ๋ ์๋์ผ๋ก ํฌ์ฐฉ๋์ด ์ฟผ๋ฆฌ์ error ์ํ๋ก ์ค์ ๋ฉ๋๋ค.
const { data, error, isError } = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const response = await fetch('https://api.example.com/todos');
if (!response.ok) {
throw new Error(`Error ${response.status}: ${response.statusText}`);
}
return response.json();
}
});
// ์ค๋ฅ ๋ฐ์ ์ ์ฒ๋ฆฌ
if (isError) {
return <div>Error: {error.message}</div>;
}
๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ฟผ๋ฆฌ ํจ์๋ฅผ ๋ณ๋์ ๋ชจ๋๋ก ์ถ์ถํ์ฌ ์ฌ์ฌ์ฉ์ฑ์ ๋์ผ ์ ์์ต๋๋ค.
// api.js
export const fetchTodos = async ({ queryKey }) => {
const [_key, { status, page }] = queryKey;
const response = await fetch(
`https://api.example.com/todos?status=${status}&page=${page}`
);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
// TodoList.jsx
const { data } = useQuery({
queryKey: ['todos', { status, page }],
queryFn: fetchTodos
});
์ด๋ฐ ๋ฐฉ์์ผ๋ก API ํธ์ถ ๋ก์ง์ ์ปดํฌ๋ํธ์์ ๋ถ๋ฆฌํ ์ ์์ด, ์ฝ๋ ๊ด๋ฆฌ์ ํ ์คํธ๊ฐ ์ฉ์ดํด์ง๋๋ค.
queryFn์ ์ด์ ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ์ ์์กดํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์๋ ์์ต๋๋ค.
// ๋จผ์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUserById(userId)
});
// ์ฌ์ฉ์ ์ ๋ณด๊ฐ ์์ ๋๋ง ํ๋ก์ ํธ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด
const { data: projects } = useQuery({
queryKey: ['projects', user?.id],
queryFn: () => fetchProjectsByUserId(user.id),
enabled: !!user // user๊ฐ ์์ ๋๋ง ํ์ฑํ
});
์ด์ฒ๋ผ enabled ์ต์ ์ ํ์ฉํ๋ฉด ์์กด์ฑ ์ฒด์ธ์ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
โ ๏ธ useQuery ์์ฝ
useQuery๋ TanStack Query์ ํต์ฌ ํ ์ผ๋ก, ์๋ฒ ๋ฐ์ดํฐ ์กฐํ์ ์ํ ๊ด๋ฆฌ๋ฅผ ๋ด๋นํฉ๋๋ค. queryKey๋ ๋ฐฐ์ด ํํ์ ๊ณ ์ ์๋ณ์๋ก, ์บ์ฑ ๋ฐ ์๋ ๋ฆฌํ์นญ์ ๊ธฐ์ค์ด ๋๋ฉฐ, ๊ณ์ธต์ ๊ตฌ์กฐ์ ๊ฐ์ฒด๋ฅผ ํ์ฉํด ํจ์จ์ ์ผ๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค. queryFn์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋น๋๊ธฐ ํจ์๋ก, ๋ด์ฅ๋ ํ๋ผ๋ฏธํฐ(queryKey, signal)๋ฅผ ํ์ฉํด ๋์ ๋ฐ์ดํฐ ํ์นญ๊ณผ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค. ์ด ๋ ์์๋ฅผ ์ ์ ํ ๊ตฌ์ฑํ๋ฉด ๋ณต์กํ ๋ฐ์ดํฐ ์๊ตฌ์ฌํญ๋ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
useMutation์ TanStack Query์์ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ ์์ ์ ์ฒ๋ฆฌํ๋ ํ ์ ๋๋ค. ๋ฐ์ดํฐ๋ฅผ ์์ฑ, ์ ๋ฐ์ดํธ, ์ญ์ ํ๋ ๋ฑ์ ์์ ์ ์ฌ์ฉ๋๋ฉฐ, useQuery์ ๋ฌ๋ฆฌ ์๋์ผ๋ก ์คํ๋์ง ์๊ณ ๋ช ์์ ์ธ ํธ์ถ์ ํตํด ์คํ๋ฉ๋๋ค.
useMutation์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ๋ mutationFn์ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ต๋๋ค. ์ด ํจ์๋ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ ๋น๋๊ธฐ ํจ์์ฌ์ผ ํฉ๋๋ค.
const { mutate, isPending, isError, isSuccess, error } = useMutation({
mutationFn: async (newTodo) => {
const response = await fetch('https://api.example.com/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTodo)
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
});
// ๋ณ์ด ์คํํ๊ธฐ
const handleSubmit = () => {
mutate({ title: 'Buy groceries', completed: false });
};
useMutation์์ ๋ฐํ๋๋ ์ฃผ์ ๊ฐ:
mutate: ๋ณ์ด๋ฅผ ์คํํ๋ ํจ์isPending: ๋ณ์ด๊ฐ ์งํ ์ค์ธ์ง ์ฌ๋ถisError: ๋ณ์ด๊ฐ ์ค๋ฅ ์ํ์ธ์ง ์ฌ๋ถisSuccess: ๋ณ์ด๊ฐ ์ฑ๊ณต ์ํ์ธ์ง ์ฌ๋ถerror: ์ค๋ฅ ๋ฐ์ ์ ์๋ฌ ๊ฐ์ฒดdata: ์ฑ๊ณต ์ ์๋ฒ ์๋ต ๋ฐ์ดํฐ๋ณ์ด ํจ์๋ ๋ค์ํ ๋ฐฉ์์ผ๋ก ๊ตฌํํ ์ ์์ต๋๋ค:
// ํจ์ ์ง์ ์ ์
const mutation = useMutation({
mutationFn: (newTodo) => addTodo(newTodo)
});
// ๋๋ ๋ ๊ฐ๋จํ๊ฒ
const mutation = useMutation({
mutationFn: addTodo
});
useMutation์ ๋ณ์ด์ ๋ค์ํ ๋จ๊ณ์์ ์์ ์ ์ํํ ์ ์๋ ์ฌ๋ฌ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: addTodo,
// ๋ณ์ด ์์ ์
onMutate: async (newTodo) => {
// ๊ด๋ จ ์ฟผ๋ฆฌ ์ทจ์
await queryClient.cancelQueries({ queryKey: ['todos'] });
// ์ด์ ์ํ ๋ฐฑ์
const previousTodos = queryClient.getQueryData(['todos']);
// ๋๊ด์ ์
๋ฐ์ดํธ
queryClient.setQueryData(['todos'], old => [...old, newTodo]);
// ๋กค๋ฐฑ์ ์ํ ์ปจํ
์คํธ ๋ฐํ
return { previousTodos };
},
// ๋ณ์ด ์ฑ๊ณต ์
onSuccess: (data, variables, context) => {
// data: ์๋ฒ ์๋ต ๋ฐ์ดํฐ
// variables: mutate์ ์ ๋ฌ๋ ๋ณ์
// context: onMutate์์ ๋ฐํ๋ ๊ฐ
// ์ฑ๊ณต ๋ฉ์์ง ํ์
toast.success('ํ ์ผ์ด ์ถ๊ฐ๋์์ต๋๋ค!');
// ํ์ ์ ํน์ ์ฟผ๋ฆฌ ๋ฌดํจํ
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
// ๋ณ์ด ์คํจ ์
onError: (error, variables, context) => {
// ๋๊ด์ ์
๋ฐ์ดํธ ๋กค๋ฐฑ
if (context?.previousTodos) {
queryClient.setQueryData(['todos'], context.previousTodos);
}
// ์ค๋ฅ ๋ฉ์์ง ํ์
toast.error(`์ค๋ฅ ๋ฐ์: ${error.message}`);
},
// ์ฑ๊ณต ๋๋ ์คํจ ํ ํญ์ ์คํ
onSettled: (data, error, variables, context) => {
// ํญ์ ์ฟผ๋ฆฌ ๋ฌดํจํ
queryClient.invalidateQueries({ queryKey: ['todos'] });
// ์ดํ ์์
์ํ
console.log('๋ณ์ด ์๋ฃ');
}
});
์ด ์ฝ๋ฐฑ ํจ์๋ค์ ์ฌ์ฉํ๋ฉด ๋ณ์ด์ ์ ์ฒด ์๋ช ์ฃผ๊ธฐ์ ๊ฑธ์ณ ์ธ๋ฐํ ์ ์ด๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
๋๊ด์ ์ ๋ฐ์ดํธ๋ ์๋ฒ ์๋ต์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ UI๋ฅผ ์ฆ์ ์ ๋ฐ์ดํธํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ๊ธฐ๋ฒ์ ๋๋ค.
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: async (todo) => {
const response = await fetch(`https://api.example.com/todos/${todo.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: todo.completed })
});
if (!response.ok) {
throw new Error('Failed to update todo');
}
return response.json();
},
// ๋๊ด์ ์
๋ฐ์ดํธ ๊ตฌํ
onMutate: async (updatedTodo) => {
// ์งํ ์ค์ธ ์ฟผ๋ฆฌ ์ทจ์
await queryClient.cancelQueries({ queryKey: ['todos'] });
// ์ด์ ์ํ ๋ฐฑ์
const previousTodos = queryClient.getQueryData(['todos']);
// ์บ์ ์
๋ฐ์ดํธ
queryClient.setQueryData(['todos'], old =>
old.map(todo =>
todo.id === updatedTodo.id
? { ...todo, completed: updatedTodo.completed }
: todo
)
);
return { previousTodos };
},
// ์ค๋ฅ ๋ฐ์ ์ ๋กค๋ฐฑ
onError: (err, updatedTodo, context) => {
// ์ด์ ์ํ๋ก ๋ณต์
queryClient.setQueryData(['todos'], context.previousTodos);
toast.error('ํ ์ผ ์
๋ฐ์ดํธ์ ์คํจํ์ต๋๋ค.');
},
// ๋ฌด์กฐ๊ฑด ์ฟผ๋ฆฌ ๋ฌดํจํ
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
}
});
// ์ฌ์ฉ ์์
const toggleTodoCompletion = (todoId, isCompleted) => {
mutate({ id: todoId, completed: isCompleted });
};
mutate ํจ์๋ ๋ค์ํ ๋ฐฉ์์ผ๋ก ํ์ฉ๋ ์ ์์ผ๋ฉฐ, ํ์์ ๋ฐ๋ผ ์ถ๊ฐ์ ์ธ ์ต์ ์ ์ ๋ฌํ ์ ์์ต๋๋ค.
// ๋จ์ ํธ์ถ
mutate(newTodo);
// ์ฑ๊ณต/์คํจ ํธ๋ค๋ฌ ํฌํจ ํธ์ถ
mutate(newTodo, {
onSuccess: (data) => {
// ์ด ํธ๋ค๋ฌ๋ ์ ์ญ onSuccess ํ์ ์คํ๋จ
console.log('Todo ์ถ๊ฐ ์ฑ๊ณต:', data);
// ์ถ๊ฐ ์์
์ํ (์: ํผ ์ด๊ธฐํ, ํ๋ฉด ์ ํ ๋ฑ)
resetForm();
navigate('/todos');
},
onError: (error) => {
console.error('Todo ์ถ๊ฐ ์คํจ:', error);
// ์ฌ์ฉ์์๊ฒ ํน์ ์ค๋ฅ ๋ฉ์์ง ํ์
}
});
๋น๋๊ธฐ ํ๋ฆ ์ ์ด๊ฐ ํ์ํ ๊ฒฝ์ฐ mutateAsync๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:
const { mutateAsync } = useMutation({
mutationFn: addTodo
});
const handleSubmit = async () => {
try {
// mutateAsync๋ Promise๋ฅผ ๋ฐํ
const newTodo = await mutateAsync({ title: 'Buy milk', completed: false });
console.log('์ ํ ์ผ์ด ์ถ๊ฐ๋จ:', newTodo);
// ์ฑ๊ณต ํ ์ถ๊ฐ ์์
await saveToLocalStorage(newTodo);
// ๋ค๋ฅธ ๋ณ์ด ์ฐ์ ์คํ
await updateUserStats();
} catch (error) {
console.error('์ค๋ฅ ๋ฐ์:', error);
}
};
๋ณ์ด ํ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ์ต์ ์ํ๋ก ์ ์งํ๊ธฐ ์ํ ์ฌ๋ฌ ์ ๋ต์ด ์์ต๋๋ค.
๊ฐ์ฅ ๋จ์ํ ๋ฐฉ๋ฒ์ ๊ด๋ จ ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํจํํ์ฌ ๋ฆฌํ์นญ์ ํธ๋ฆฌ๊ฑฐ ํ๋ ๊ฒ์ ๋๋ค:
const { mutate } = useMutation({
mutationFn: addTodo,
onSuccess: () => {
// 'todos'๋ก ์์ํ๋ ๋ชจ๋ ์ฟผ๋ฆฌ ๋ฌดํจํ
queryClient.invalidateQueries({ queryKey: ['todos'] });
}
});
๋๋ก๋ ์๋ฒ ์๋ต์ ์ฌ์ฉํ์ฌ ์บ์๋ฅผ ์ง์ ์ ๋ฐ์ดํธํ๋ ๊ฒ์ด ํจ์จ์ ์ผ ์ ์์ต๋๋ค:
const { mutate } = useMutation({
mutationFn: addTodo,
onSuccess: (newTodo) => {
// ๊ธฐ์กด 'todos' ์ฟผ๋ฆฌ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
const previousTodos = queryClient.getQueryData(['todos']);
// ์ ํ ์ผ์ ํฌํจํ์ฌ ์บ์ ์
๋ฐ์ดํธ
if (previousTodos) {
queryClient.setQueryData(['todos'], [...previousTodos, newTodo]);
}
// ์ ํ ์ผ์ ์์ธ ์ฟผ๋ฆฌ ๋ฏธ๋ฆฌ ์ฑ์ฐ๊ธฐ
queryClient.setQueryData(['todo', newTodo.id], newTodo);
}
});
๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ฌ๋ฌ ์ ๋ต์ ๊ฒฐํฉํ๋ ๊ฒ์ด ์ ์ฉํ ์ ์์ต๋๋ค:
const { mutate } = useMutation({
mutationFn: updateTodo,
onSuccess: (updatedTodo) => {
// 1. ์
๋ฐ์ดํธ๋ ํ ์ผ์ ์์ธ ์ฟผ๋ฆฌ ์ง์ ์
๋ฐ์ดํธ
queryClient.setQueryData(['todo', updatedTodo.id], updatedTodo);
// 2. ๋ชฉ๋ก ์ฟผ๋ฆฌ๋ฅผ ์ง์ ์
๋ฐ์ดํธ (ํน์ ์กฐ๊ฑด์ ๋ง๋ ๊ฒฝ์ฐ)
queryClient.setQueriesData({ queryKey: ['todos'] }, (old) => {
if (!old) return old;
return old.map(todo =>
todo.id === updatedTodo.id ? updatedTodo : todo
);
});
// 3. ๊ด๋ จ ์ฟผ๋ฆฌ ๋ฌดํจํ (ํํฐ๋ง๋ ๋ชฉ๋ก ๋ฑ)
queryClient.invalidateQueries({
queryKey: ['todos'],
// ํน์ ์ฟผ๋ฆฌ๋ง ๋ฌดํจํ
predicate: (query) =>
query.queryKey.length > 1 &&
typeof query.queryKey[1] === 'object'
});
}
});
์ด๋ฌํ ๋ค์ํ ์ ๋ต์ ์ํฉ์ ๋ง๊ฒ ํ์ฉํ๋ฉด ํจ์จ์ ์ด๊ณ ์ผ๊ด๋ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
โ ๏ธ useMutation ์์ฝ
useMutation์ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ ์์ ์ ์ํ ํ ์ผ๋ก, mutationFn์ ํตํด ์ค์ API ํธ์ถ์ ์ ์ํ๊ณ mutate ํจ์๋ก ์คํํฉ๋๋ค. ์๋ช ์ฃผ๊ธฐ ์ฝ๋ฐฑ(onMutate, onSuccess, onError, onSettled)์ ํ์ฉํ ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ํตํด ์ฆ๊ฐ์ ์ธ UI ์๋ต์ ์ ๊ณตํ๊ณ , queryClient์ ๋ค์ํ ๋ฉ์๋(invalidateQueries, setQueryData)๋ฅผ ์ฌ์ฉํด ๊ด๋ จ ์ฟผ๋ฆฌ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ฐฑ์ ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๊ธฐ๋ฅ๋ค์ ํ์ฉํ๋ฉด ๋ณต์กํ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์๋๋ฆฌ์ค์์๋ ์ผ๊ด๋ ์ํ ๊ด๋ฆฌ์ ํฅ์๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์ ๋น๋๊ธฐ ํต์ ์ ๋จ์ํ ๋ฐ์ดํฐ ๊ตํ์ ๋์ด ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์กฐ์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ํฅ์ ๋ฏธ์น๋ ํต์ฌ ์์์ ๋๋ค. ๊ธฐ๋ณธ JavaScript์ Promise์ async/await, ๊ทธ๋ฆฌ๊ณ Fetch API๋ ๋น๋๊ธฐ ํต์ ์ ๊ธฐ์ด๋ฅผ ์ ๊ณตํ์ง๋ง, ์ค์ ์๋น์ค์๋ ๋ ๋ณต์กํ ์๊ตฌ์ฌํญ์ด ๋ฐ๋ฆ ๋๋ค. TanStack Query๋ ์ด๋ฌํ ๊ณ ๊ธ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑ์ํค๋ ๊ฐ๋ ฅํ ๋๊ตฌ๋ก, ๋ฐ์ดํฐ ์บ์ฑ๊ณผ ์ํ ๊ด๋ฆฌ๋ฅผ ํตํด ๊ฐ๋ฐ์ ๊ฒฝํ๊ณผ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ๋ชจ๋ ํฅ์์ํต๋๋ค.
TanStack Query์ staleTime/gcTime์ผ๋ก ๋ฐ์ดํฐ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํ๊ณ , queryKey๋ฅผ ํตํด ํจ์จ์ ์ธ ์บ์ฑ ์ ๋ต์ ๊ตฌํํ๋ฉฐ, useQuery์ useMutation์ผ๋ก ๋ฐ์ดํฐ ์กฐํ์ ๋ณ๊ฒฝ์ ์ง๊ด์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ๋๊ด์ ์ ๋ฐ์ดํธ, ์๋ ๋ฆฌํ์นญ, ์์กด์ ์ฟผ๋ฆฌ์ ๊ฐ์ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ๋ณต์กํ ๋ฐ์ดํฐ ํ๋ฆ์ ๊ฐ์ํํ๊ณ ์ฌ์ฉ์์๊ฒ ๋ ์ ๋ คํ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค. ๊ฒฐ๊ตญ Fetch API์์ TanStack Query๋ก์ ์ ํ์ ๋จ์ํ ๋๊ตฌ๋ฅผ ๋ฐ๊พธ๋ ๊ฒ์ด ์๋๋ผ, ์๋ฒ ์ํ๋ฅผ ๋ฐ๋ผ๋ณด๋ ๊ด์ ์์ฒด๋ฅผ ์ ํํ๋ ํจ๋ฌ๋ค์์ ๋ณํ๋ผ๊ณ ํ ์ ์๊ฒ ์ต๋๋ค.
Reference:
1. https://joshua1988.github.io/web-development/javascript/promise-for-beginners/
2. https://ko.javascript.info/async-await
3. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
4. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
5. https://developer.mozilla.org/en-US/docs/Web/API/Response/json
6. https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults
7. https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults
8. https://tanstack.com/query/latest/docs/framework/react/guides/query-keys
9. https://tanstack.com/query/latest/docs/framework/react/guides/query-functions
10. https://tanstack.com/query/latest/docs/framework/react/reference/useQuery
11. https://tanstack.com/query/latest/docs/framework/react/reference/useMutation