๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป ํ”„๋กœ์ ํŠธ/๐Ÿ‘‘ VIP ์ดˆ๋Œ€์žฅ ๐Ÿ’Œ

[Spring, React] react-query ์ถ”๊ฐ€ ํ›„ ๊ธฐ๋Šฅ ๋จนํ†ต ์˜ค๋ฅ˜ ๋ฐœ์ƒ,,, ํ•ด๊ฒฐํ•ด๋ณด์ž!

by hyeong._.ing 2026. 5. 26.

 

 

๋ฆฌ์•กํŠธ์— ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ ๋’ค
๊ฒ€์ƒ‰์ฐฝ์— ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด
์™„์ „ํžˆ ํ™”๋ฉด์ด ๋ฉˆ์ถฐ๋ฒ„๋ฆฌ๋Š” ์ด์ƒํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.
์™œ ๊ทธ๋Ÿฐ์ง€ ์ฐพ์•„๋ณด๊ณ  ํ•ด๊ฒฐํ•ด๋ณด์ž.

 

 

 

 

1. ๋ฌธ์ œ ์ƒํ™ฉ

๋ฐฉํ™ฉํ•˜๋Š” ๋งˆ์šฐ์Šค

 

๋งˆ์ง€๋ง‰์œผ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฒ€ํ† ํ•˜๋ฉด์„œ ์†Œ์†Œํ•˜๊ฒŒ ์ˆ˜์ •ํ•ด์•ผํ•  ๋ถ€๋ถ„๋ถ€ํ„ฐ ๋ถˆํŽธํ–ˆ๋˜ ์ , ์•„์‰ฌ์› ๋˜ ๋ถ€๋ถ„ ๋“ฑ ์—ฌ๋Ÿฌ๊ตฐ๋ฐ ์ˆ˜์ •์„ ์ง„ํ–‰ํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ทธ ๋’ค๋ถ€ํ„ฐ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•˜์ง€ ์•Š์•˜๋‹ค. ์—ฌ๊ธฐ์„œ ๋‚˜์˜ ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ๋Š”๋ฐ, ๊ณ ์น  ๋•Œ๋งˆ๋‹ค ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ํ™•์ธํ–ˆ์–ด์•ผํ–ˆ์ง€๋งŒ ๊ฒ€์ƒ‰๋งŒ ๋นผ๊ณ  ํ™•์ธํ•˜๋Š” ๋ฐ”๋žŒ์— ์ด ๋ฌธ์ œ๊ฐ€ ์–ธ์ œ๋ถ€ํ„ฐ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜์—ˆ๋Š”์ง€ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์—ˆ๋‹ค.

F12๋ฅผ ๋ˆŒ๋Ÿฌ์„œ network๋ฅผ ํ†ตํ•ด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€, ์™œ ๋จนํ†ต์ด ๋œ๊ฑด์ง€ ์•Œ๊ณ  ์‹ถ์–ด๋„ F12์กฐ์ฐจ ๋ˆŒ๋ฆฌ์ง€ ์•Š์•˜๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋„ ์•Š๊ณ  run์€ ์ž˜ ๋Œ์•„๊ฐ€๊ณ  ์žˆ์—ˆ๊ธฐ์— ์‰ฝ๊ฒŒ ์ด์œ ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ๋‹ค.

 

 

 

 


 

 

 

 

2. ๋ฌธ์ œ์˜ ์›์ธ ์ฐพ๊ธฐ

  • Elasticsearch ํ˜น์€ Docker ๋ฌธ์ œ?
    ์ด ํ”„๋กœ์ ํŠธ์—” ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์ด ๋‘๊ตฐ๋ฐ์— ์กด์žฌํ•œ๋‹ค. ๊ณ ๊ฐ ํŒŒํŠธ์™€ ๊ด€๋ฆฌ์ž ํŒŒํŠธ ์ด ๋‘๊ตฐ๋ฐ์—์„œ ๊ฒ€์ƒ‰์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ค‘ ๊ณ ๊ฐ ํŒŒํŠธ์—๋งŒ Elasticsearch๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๋งŒ์•ฝ Docker๋‚˜ Elasticsearch ๋ฌธ์ œ๋ผ๋ฉด ๊ด€๋ฆฌ์ž ํŒŒํŠธ ์ชฝ ๊ฒ€์ƒ‰ ๋ถ€๋ถ„์€ ๋ฌธ์ œ์—†์ด ๋Œ์•„๊ฐˆ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๊ด€๋ฆฌ์ž ์ชฝ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ํ™•์ธํ•ด๋ณธ ๊ฒฐ๊ณผ, ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ์ž์ฒด์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด ๊ฒƒ์„ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค. ๊ด€๋ฆฌ์ž ํŒŒํŠธ ์ชฝ ๊ฒ€์ƒ‰์—์„œ๋„ ๋จนํ†ต์ด ๋˜๋ฉฐ ์™„์ „ํžˆ ๋ฉˆ์ถฐ๋ฒ„๋ฆฌ๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ–ˆ๋‹ค. 


  • ๋ฐฑ์—”๋“œ ๋ฌธ์ œ? ํ”„๋ก ํŠธ ๋ฌธ์ œ?
    ๋ฐฑ์—”๋“œ๊ฐ€ ๋ฌธ์ œ์ธ์ง€, ํ”„๋ก ํŠธ๊ฐ€ ๋ฌธ์ œ์ธ์ง€ ์•Œ์•„๋‚ด์•ผ ํ–ˆ๋‹ค. ์ผ๋‹จ F12๊ฐ€ ๋ˆŒ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค๋Š”๊ฒŒ ํฐ ํžŒํŠธ๊ฐ€ ๋˜์—ˆ๋‹ค. ์ด๊ฑด ๋ณดํ†ต ํ”„๋ก ํŠธ ๋ฌธ์ œ์˜€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ˆ˜์ •ํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฐฑ์—”๋“œ๋Š” ๊ฑฐ์˜ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ”„๋ก ํŠธ ๋ฌธ์ œ์— ์ดˆ์ ์„ ๋งž์ถœ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.


  • ํ”„๋ก ํŠธ ๊ฒ€์ƒ‰ ์ฝ”๋“œ์™€ ๊ด€๋ จ๋œ ์ˆ˜์ • ์‚ฌํ•ญ
    ๊ฒ€์ƒ‰ ์ฝ”๋“œ๋ฅผ ๊ฑด๋“œ๋ ธ๋˜ ๊ฒฝ์šฐ๋Š” ๋”ฑ ๋‘ ๋ฒˆ, ๊ทธ ์ค‘ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ๋งŒํ•œ ๊ฑด ํ•˜๋‚˜ ๋ฐ–์— ์—†์—ˆ๋‹ค. react-query ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ [ ์ฆ‰์‹œ ๊ฒ€์ƒ‰ API ํ˜ธ์ถœ ] ๋ฐฉ์‹์—์„œ [ ์ƒํƒœ๊ฐ’ ๋ณ€๊ฒฝ ํ›„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋‹ค์‹œ ์กฐํšŒ ] ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊ฟจ์—ˆ๋‹ค.

    ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๋ฉด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ ์ „์—๋Š” [ ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ -> ์—”ํ„ฐ ๋ฒ„ํŠผ -> /search ์ง์ ‘ ํ˜ธ์ถœ -> ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”์— ๋„ฃ์Œ ] ์ˆœ์„œ๋กœ ํ๋ฆ„์ด ์•„์ฃผ ๋‹จ์ˆœํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ [ ๊ฒ€์ƒ‰์–ด ์ƒํƒœ๊ฐ’ ๋ณ€๊ฒฝ -> react๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง -> react-query๊ฐ€ queryKey ๋ณ€๊ฒฝ ๊ฐ์ง€ -> ๊ฒ€์ƒ‰ API ํ˜ธ์ถœ -> ๊ฒฐ๊ณผ ๋ฐ›๊ณ  ํ…Œ์ด๋ธ” ๊ฐฑ์‹  ]์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค. 

    ์ฝ”๋“œ ํ๋ฆ„์ƒ์œผ๋กœ ๋ณด์•˜์„ ๋• ์–ด๋””๊ฐ€ ๋ฌธ์ œ์ธ์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค. ์ด ํ๋ฆ„ ์ค‘ ์–ด๋””์„œ ๊ผฌ์˜€๋Š”์ง€ ์‚ดํŽด๋ณด๊ณ  ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด๋ณด์ž.

 

 

 


 

 

 

 

3. ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

  • ์ˆ˜์ • ์ „ ์ฝ”๋“œ(react-query)
const [searchKeyword, setSearchKeyword] = useState("");

const { data: customers = [] } = useQuery({
  queryKey: ["customers", searchKeyword],
  queryFn: () => fetchCustomers(searchKeyword),
});

const handleSearch = (keyword) => {
  setSearchKeyword(keyword);
  setIsSearchOpen(false);
AI๋Š” searchKeyword๊ฐ€ ๋ฐ”๋€Œ๋Š” ์ˆœ๊ฐ„ react-query๊ฐ€ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ์กฐํšŒํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ, ๊ตฌ์กฐ์ƒ ํ‹€๋ฆฐ ๊ฑด ์•„๋‹ˆ์ง€๋งŒ ๊ฒ€์ƒ‰์ฐฝ ๋‹ซ๊ธฐ/๋ Œ๋”๋ง/ํ…Œ์ด๋ธ” ๊ฐฑ์‹ ์ด ๋™์‹œ์— ์ผ์–ด๋‚˜๋ฉด์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ–ˆ๋‹ค.

 

const [searchKeyword, setSearchKeyword] = useState("");
const [submittedKeyword, setSubmittedKeyword] = useState("");

const {
  data: customers = [],
  isLoading,
  isFetching,
  isError,
  error,
  refetch,
} = useQuery({
  queryKey: ["customers", submittedKeyword],
  queryFn: () => fetchCustomers(submittedKeyword),
  enabled: canRead,
});

const handleSearch = (keyword) => {
  const trimmed = keyword.trim();

  if (!trimmed) {
    alert("๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.");
    return;
  }

  setSubmittedKeyword(trimmed);
  setIsSearchOpen(false);
  setPagination((prev) => ({ ...prev, pageIndex: 0 }));
};
๊ทธ๋ž˜์„œ ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ searchKeyword(๊ฒ€์ƒ‰์ฐฝ์— ์ž…๋ ฅ ์ค‘์ธ ๊ฐ’)์™€ submittedkeyword(์‹ค์ œ API ์กฐํšŒ์— ์‚ฌ์šฉํ•˜๋Š” ๊ฐ’)๋ฅผ ๋„ฃ์–ด ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค.  ์ด๋Ÿฌ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค ์ฟผ๋ฆฌ๊ฐ€ ํ”๋“ค๋ฆฌ์ง€ ์•Š๊ณ  ์—”ํ„ฐ๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ๋งŒ submittedKeyword๋ฅผ ๋ฐ”๊ฟ”์„œ react-query๊ฐ€ ๊ฒ€์ƒ‰ API๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ,,, ์ด๋ ‡๊ฒŒ ํ•ด๋„ ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ๊ฑฐ๊ธฐ๋‹ค๊ฐ€ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋” ํฐ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ์™œ ์ด๋Ÿฐ ํ˜„์ƒ์ด ์ƒ๊ธด๊ฑด์ง€ AI๋„ ๋ชจ๋ฅธ๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ๊ทธ๋‹ˆ๊นŒ ์ฝ”๋ฑ์Šค์—๊ฒŒ ํ˜„์žฌ ์ฝ”๋“œ์— ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋ฅผ ์•Œ๋ ค์ฃผ๊ณ , ๊ทธ ๋ถ€๋ถ„์— ๋งž์ถฐ ์‹ค์ œ๋กœ ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”์ง€ ๋ฌผ์–ด๋ณด๊ณ , ์ˆ˜์ •ํ•˜๊ณ  ์ง์ ‘ ๋Œ๋ ค๋ณด๋„๋ก ์‹œ์ผฐ๋Š”๋ฐ ์ฝ”๋ฑ์Šค ์ชฝ์—์„œ๋Š” ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์ด ๋ฌธ์ œ์—†์ด ๋Œ์•„๊ฐ„๋‹ค๋Š” ๋ง์„ ์ „ํ•ด์ฃผ์—ˆ๋‹ค.

๋‚˜์ค‘์—” ๋ฐฑ์—”๋“œ ๋ฌธ์ œ์ธ๊ฐ€ ์‹ถ์—ˆ์ง€๋งŒ, ์ด๊ฑด ๋‹น์—ฐํžˆ ์•„๋‹ˆ์—ˆ๋‹ค. ์• ์ดˆ์— ๋ฐฑ์—”๋“œ์— ์š”์ฒญ์ด ์˜ค๋Š” ๊ฒƒ ๊ฐ™์ง€๋„ ์•Š์•˜๋‹ค. ๋ช‡์‹œ๊ฐ„์„ ํ—ค๋งค๋‹ค๊ฐ€ ๊ฒฐ๊ตญ ํŠœ๋‹์˜ ๋์€ ์ˆœ์ •์ด๋ž€... ๋ง(?)์ฒ˜๋Ÿผ ๊ฒ€์ƒ‰ ํŒŒํŠธ๋งŒ ์›๋ž˜์˜ ๋‹จ์ˆœ ์ฝ”๋“œ๋กœ ๋ฐ”๊พธ๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

 

 

  • ์ˆ˜์ • ํ›„ ์ฝ”๋“œ

const handleSearch = async (keyword) => {
  const res = await authFetch(`/api/customers/search?keyword=${encodeURIComponent(keyword)}`);
  const data = await res.json();

  dispatch({ type: "INIT", payload: data });
  setIsSearchOpen(false);
  setPage(1);
};
์•„์ฃผ ์ž˜ ์•„๋Š” ์ฝ”๋“œ๊ฐ€ ๋‚˜์™”๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์—ญ์‹œ๋‚˜ ์ฝ”๋“œ๋ฅผ ์˜ฎ๊ฒจ์‹ฌ์ž๋งˆ์ž ์ž‘๋™์ด ์•„์ฃผ ์ž˜๋๋‹ค.

์ด ์ฝ”๋“œ๋Š” ๋งจ ์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ [ ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ -> ์—”ํ„ฐ ๋ฒ„ํŠผ -> /api/customers/search ์ง์ ‘ ํ˜ธ์ถœ -> ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”์— ๋„ฃ์Œ ] ์ˆœ์„œ๋กœ ์ง„ํ–‰๋˜๋Š” ํ๋ฆ„์ด ์•„์ฃผ ๋‹จ์ˆœํ•œ ์ฝ”๋“œ์ด๋‹ค.

์‚ฌ์‹ค ์™œ ๋™์ž‘์ด ์•ˆ๋œ๊ฑด์ง€ ๋ฌด์ฒ™ ๊ถ๊ธˆํ•˜๋‹ค. ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์— elasticsearch๊ฐ€ ์žˆ์–ด์„œ ๊ทธ๋ ‡์ง€ ๊ทธ๋‹ค์ง€ ๋ณต์žกํ•œ ์ฝ”๋“œ๋„ ์•„๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹น์—ฐํ•˜๊ฒŒ ์ž‘๋™๋˜์ง€ ์•Š์„๊ฑฐ๋ž€ ์ƒ๊ฐ์ด ์—†์—ˆ๋‹ค. ์†”์งํžˆ ์ •ํ™•ํ•œ ์›์ธ์„ ์ฐพ์•„๋‚ด์ง€ ๋ชปํ•ด์„œ ์•„์‰ฝ๊ธด ํ•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋ ‡๊ฒŒ๋ผ๋„ ๊ณ ์น  ์ˆ˜ ์žˆ์—ˆ์œผ๋‹ˆ ๋‹คํ–‰์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด์„œ, ๊ผญ ์ฝ”๋“œ ์ˆ˜์ • ํ›„์—” ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค๋ณด๊ณ  ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๊ฒ€์‚ฌํ•˜๋Š” ์Šต๊ด€์„ ์žŠ์ง€ ์•Š๊ธฐ๋กœ ๊ฒฐ์‹ฌํ–ˆ๋‹ค!!