{"id":776,"date":"2026-05-18T23:01:28","date_gmt":"2026-05-18T15:01:28","guid":{"rendered":"https:\/\/junai.ai\/blog\/react-custom-hooks-12\/"},"modified":"2026-05-19T20:26:19","modified_gmt":"2026-05-19T12:26:19","slug":"react-custom-hooks-12","status":"publish","type":"post","link":"https:\/\/junai.ai\/blog\/react-custom-hooks-12\/","title":{"rendered":"React \ucee4\uc2a4\ud140 Hook \ub9cc\ub4e4\uae30 (Ch.12)"},"content":{"rendered":"\n<!-- WordPress REST API \ubc1c\ud589\uc6a9 HTML (\uc790\ub3d9 \uc0dd\uc131) -->\n<!-- WP-FEATURED-MEDIA-ID: 751 -->\n<div style=\"max-width:800px;margin:0 auto;\">\n<style>\n:root {--color-primary:#0891b2;--color-accent:#06b6d4;--color-bg:#f8fafc;--color-bg-card:#fff;--color-text:#0f172a;--color-text-muted:#64748b;--hero-start:#0f172a;--hero-end:#0891b2;}\n*{box-sizing:border-box;}\n.container{max-width:760px;margin:0 auto;padding:0 24px 80px;}\n.hero{background:linear-gradient(135deg,var(--hero-start) 0%,var(--hero-end) 100%);color:#fff;padding:80px 24px 60px;text-align:center;}\n.hero .eyebrow{display:inline-block;font-size:14px;color:#67e8f9;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;margin-bottom:14px;}\n.hero h1{font-size:36px;margin:0 0 16px;line-height:1.3;font-weight:800;}\n.hero p{color:#cffafe;font-size:18px;max-width:640px;margin:0 auto;line-height:1.6;}\n.hero img{width:100%;max-width:640px;height:auto;margin:32px auto 0;border-radius:10px;display:block;}\narticle{padding-top:48px;}\narticle h2{font-size:26px;margin:56px 0 20px;padding-left:14px;border-left:5px solid var(--color-accent);line-height:1.4;}\narticle h3{font-size:19px;margin:32px 0 12px;color:var(--color-primary);}\narticle p{margin:16px 0;}\narticle strong{color:var(--color-primary);font-weight:700;}\narticle code{background:#ecfeff;padding:2px 8px;border-radius:4px;font-family:'SF Mono',Menlo,Consolas,monospace;font-size:14px;color:#0e7490;}\n.databox{background:#ecfeff;border-left:4px solid var(--color-accent);padding:16px 20px;margin:24px 0;border-radius:0 8px 8px 0;font-size:15.5px;}\n.databox strong{color:var(--color-primary);}\n.warnbox{background:linear-gradient(135deg,#fef3c7 0%,#fde68a 100%);padding:16px 20px;margin:24px 0;border-radius:8px;font-size:15.5px;}\n.code-block{background:#0f172a;color:#e2e8f0;padding:16px 20px;border-radius:8px;font-family:'SF Mono',Menlo,Consolas,monospace;font-size:14px;line-height:1.6;margin:20px 0;overflow-x:auto;white-space:pre;}\n.cta{background:linear-gradient(135deg,#0891b2 0%,#06b6d4 100%);color:#fff;padding:28px 24px;border-radius:12px;margin:48px 0 0;text-align:center;}\n.cta h3{color:#fff;margin:0 0 8px;font-size:20px;}\n.cta p{color:#cffafe;margin:0;font-size:15.5px;}\n.footer-nav{margin-top:32px;padding-top:20px;border-top:1px solid #e2e8f0;font-size:14px;color:var(--color-text-muted);}\n.footer-nav a{color:var(--color-primary);text-decoration:none;}\n@media (max-width:480px){.hero h1{font-size:26px;}.hero p{font-size:16px;}article h2{font-size:21px;}article h3{font-size:17px;}body{font-size:16px;}}\n<\/style>\n<section class=\"hero\">\n  <span class=\"eyebrow\">React \uad50\uc7ac \u00b7 \uc911\uae09 12\ud3b8<\/span>\n  <h1>React \ucee4\uc2a4\ud140 Hook \ub9cc\ub4e4\uae30<\/h1>\n  <p>\uac19\uc740 useEffect\u00b7useState \ud328\ud134\uc774 \uc5ec\ub7ec \ucef4\ud3ec\ub10c\ud2b8\uc5d0 \ubc18\ubcf5? Hook \uc73c\ub85c \ucd94\ucd9c\ud574 \ud55c \ubc88\uc5d0 \uc815\ub9ac.<\/p>\n  <img decoding=\"async\" src=\"https:\/\/junai.ai\/blog\/wp-content\/uploads\/2026\/05\/hero-5-34.jpg\" alt=\"\ubd80\ubaa8 \ucf54\ub4dc \uc5d0\ub514\ud130\uc5d0\uc11c \uc791\uc740 \uc7ac\uc0ac\uc6a9 \ucef4\ud3ec\ub10c\ud2b8 \uc544\uc774\ucf58\uc774 \ucd94\ucd9c\ub418\uc5b4 \ub3c4\uad6c \uc0c1\uc790\uc5d0 \ub4e4\uc5b4\uac00\ub294 \uc77c\ub7ec\uc2a4\ud2b8 \u2014 \ucee4\uc2a4\ud140 Hook \ucee8\uc149\">\n<\/section>\n\n<div class=\"container\">\n<article>\n\n<p>11\ud3b8 useEffect \uc758 \ub370\uc774\ud130 fetch \uc608\uc81c \u2014 \ub2e4\uc74c \ucef4\ud3ec\ub10c\ud2b8\uc5d0\uc11c \ub611\uac19\uc774 \ub610 \uc4f0\uac8c \ub41c\ub2e4. \uc138 \ubc88\uc9f8 \ucef4\ud3ec\ub10c\ud2b8\uc5d0\uc11c\ub3c4, \ub124 \ubc88\uc9f8\uc5d0\uc11c\ub3c4. \uac19\uc740 try\u00b7catch\u00b7loading state 5\uc904\uc744 \ub2e4\uc12f \uad70\ub370\uc5d0 \ubcf5\ubd99\ud558\uba74 \uc720\uc9c0\ubcf4\uc218\uac00 \uc9c0\uc625. \ub2f5\uc740 <strong>\ucee4\uc2a4\ud140 Hook<\/strong>.<\/p>\n\n<p>\uc774\ubc88 12\ud3b8\uc740 \ucee4\uc2a4\ud140 Hook \uc758 \ub2e8 \ud558\ub098\uc758 \uaddc\uce59 (use- prefix) + \uc2e4\uc804 3\uac1c (<code>useFetch<\/code>\u00b7<code>useLocalStorage<\/code>\u00b7<code>useDebounce<\/code>) + \ud68c\uc0ac \ucf54\ub4dc\ubca0\uc774\uc2a4\uc5d0 \ub450\uae30 \uc88b\uc740 \ud328\ud134\uae4c\uc9c0.<\/p>\n\n<h2>1. \ucee4\uc2a4\ud140 Hook \uc740 &#8220;use \ub85c \uc2dc\uc791\ud558\ub294 \ud568\uc218&#8221; \uc77c \ubfd0<\/h2>\n\n<p>\ud2b9\ubcc4\ud55c \ubb38\ubc95 \uc5c6\uc74c. \ud568\uc218 \uc774\ub984\uc774 <code>use<\/code> \ub85c \uc2dc\uc791 + \uadf8 \uc548\uc5d0\uc11c React Hook \uc744 \ud638\ucd9c\ud558\uba74 \ub05d. ESLint \uaddc\uce59 <code>react-hooks<\/code> \uac00 \uc774 \uaddc\uce59\uc73c\ub85c \uac80\uc0ac.<\/p>\n\n<div class=\"code-block\">\/\/ useCounter.ts\nimport { useState } from &#8216;react&#8217;;\n\nexport function useCounter(initial = 0) {\n  const [count, setCount] = useState(initial);\n  const increment = () =&gt; setCount(c =&gt; c + 1);\n  const decrement = () =&gt; setCount(c =&gt; c &#8211; 1);\n  const reset = () =&gt; setCount(initial);\n  return { count, increment, decrement, reset };\n}\n\n\/\/ \uc0ac\uc6a9\nfunction App() {\n  const { count, increment, reset } = useCounter(0);\n  return (\n    &lt;&gt;\n      &lt;p&gt;{count}&lt;\/p&gt;\n      &lt;button onClick={increment}&gt;+&lt;\/button&gt;\n      &lt;button onClick={reset}&gt;reset&lt;\/button&gt;\n    &lt;\/&gt;\n  );\n}<\/div>\n\n<p>\uc774\uac8c \ucee4\uc2a4\ud140 Hook \uc758 \uc804\ubd80. \ucef4\ud3ec\ub10c\ud2b8\uac00 \uc544\ub2c8\ub77c \ud568\uc218, \ubc18\ud658\uc740 JSX \uac00 \uc544\ub2c8\ub77c \uac12\/\uac1d\uccb4.<\/p>\n\n<div class=\"databox\">\n<strong>\uc65c use- prefix \uac00 \uac15\uc81c\uc778\uac00<\/strong> \u2014 React \uac00 Hook \uc758 \ud638\ucd9c \uc21c\uc11c\ub97c \ucd94\uc801\ud574 state \ub97c \uc778\uc2a4\ud134\uc2a4\ubcc4\ub85c \ubd84\ub9ac. use- \ub85c \uc2dc\uc791\ud558\uc9c0 \uc54a\uc73c\uba74 React \uc758 \uc815\uc801 \ubd84\uc11d\uc774 &#8220;\uc774 \ud568\uc218\uac00 Hook \uc778\uc9c0&#8221; \ubaa8\ub984 \u2192 Hook rule \uc704\ubc18 \ubabb \uc7a1\uc74c. <code>getCounter<\/code> \ub77c\uace0 \uc774\ub984 \uc9c0\uc73c\uba74 \ucef4\ud30c\uc77c\uc740 \ub418\uc9c0\ub9cc ESLint \uac00 \uc5d0\ub7ec.\n<\/div>\n\n<h2>2. useFetch \u2014 11\ud3b8 useEffect \uc758 \uc9c4\uc9dc \uc751\uc6a9<\/h2>\n\n<p>11\ud3b8\uc758 fetch + loading + error \ud328\ud134\uc744 Hook \uc73c\ub85c \ucd94\ucd9c.<\/p>\n\n<div class=\"code-block\">\/\/ useFetch.ts\nimport { useState, useEffect } from &#8216;react&#8217;;\n\nexport function useFetch&lt;T&gt;(url: string) {\n  const [data, setData] = useState&lt;T | null&gt;(null);\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState&lt;Error | null&gt;(null);\n\n  useEffect(() =&gt; {\n    let cancelled = false;\n    setLoading(true);\n    fetch(url)\n      .then(r =&gt; r.json())\n      .then(json =&gt; { if (!cancelled) setData(json); })\n      .catch(err =&gt; { if (!cancelled) setError(err); })\n      .finally(() =&gt; { if (!cancelled) setLoading(false); });\n    return () =&gt; { cancelled = true; };  \/\/ cleanup\n  }, [url]);\n\n  return { data, loading, error };\n}\n\n\/\/ \uc0ac\uc6a9 \u2014 \ud55c \uc904\nfunction UserProfile({ id }) {\n  const { data: user, loading, error } = useFetch&lt;User&gt;(`\/api\/users\/${id}`);\n  if (loading) return &lt;Spinner \/&gt;;\n  if (error) return &lt;ErrorMsg error={error} \/&gt;;\n  return &lt;h1&gt;{user.name}&lt;\/h1&gt;;\n}<\/div>\n\n<p><code>cancelled<\/code> \ud50c\ub798\uadf8\uac00 \ud575\uc2ec \u2014 URL \uc774 \ube68\ub9ac \ubc14\ub00c\uba74 \uc61b fetch \uc751\ub2f5\uc774 \ub2a6\uac8c \uc640\uc11c \uc0c8 \ub370\uc774\ud130\ub97c \ub36e\uc5b4\uc4f0\ub294 race condition \uc744 \ubc29\uc9c0. cleanup function \uc73c\ub85c cancelled \ub97c true \ub85c \u2192 \uc61b fetch \uc751\ub2f5 \ubb34\uc2dc.<\/p>\n\n<h2>3. useLocalStorage \u2014 useState \uc758 \uc601\uc18d \ubc84\uc804<\/h2>\n\n<p>state \uac00 \uc0c8\ub85c\uace0\uce68 \ud6c4\uc5d0\ub3c4 \uc720\uc9c0\ub418\uc5b4\uc57c \ud560 \ub54c (\ud14c\ub9c8 \uc124\uc815\u00b7\ub85c\uadf8\uc778 \ud1a0\ud070\u00b7draft). useState \uc640 \uac19\uc740 \uc778\ud130\ud398\uc774\uc2a4, \uc548\uc740 localStorage.<\/p>\n\n<div class=\"code-block\">\/\/ useLocalStorage.ts\nexport function useLocalStorage&lt;T&gt;(key: string, initial: T) {\n  const [value, setValue] = useState&lt;T&gt;(() =&gt; {\n    const saved = localStorage.getItem(key);\n    return saved ? JSON.parse(saved) : initial;\n  });\n\n  useEffect(() =&gt; {\n    localStorage.setItem(key, JSON.stringify(value));\n  }, [key, value]);\n\n  return [value, setValue] as const;\n}\n\n\/\/ \uc0ac\uc6a9 \u2014 useState \uc640 \ub3d9\uc77c \uc778\ud130\ud398\uc774\uc2a4\nfunction Settings() {\n  const [theme, setTheme] = useLocalStorage(&#8216;theme&#8217;, &#8216;light&#8217;);\n  return &lt;button onClick={() =&gt; setTheme(t =&gt; t === &#8216;light&#8217; ? &#8216;dark&#8217; : &#8216;light&#8217;)}&gt;{theme}&lt;\/button&gt;;\n}<\/div>\n\n<p><code>useState<\/code> \ucd08\uae30\uac12\uc73c\ub85c <strong>\ud568\uc218<\/strong>\ub97c \uc804\ub2ec\ud558\ub294 \ud328\ud134 (lazy initialization). \ucef4\ud3ec\ub10c\ud2b8\uac00 \ub9c8\uc6b4\ud2b8\ub420 \ub54c \ud55c \ubc88\ub9cc localStorage \uc77d\uc74c. \ub9e4 \ub80c\ub354\ub9c8\ub2e4 \uc77d\uc73c\uba74 \uc131\ub2a5 \uc190\ud574.<\/p>\n\n<h2>4. useDebounce \u2014 \uc785\ub825 \ud3ed\uc8fc \ud761\uc218<\/h2>\n\n<p>\uac80\uc0c9 input \u2014 \uc0ac\uc6a9\uc790\uac00 \ud0c0\uc774\ud551\ud560 \ub54c\ub9c8\ub2e4 API \ud638\ucd9c\ud558\uba74 \uc548 \ub428. 0.5\ucd08 \uba48\ucd9c \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9b0 \ud6c4 \ud55c \ubc88\ub9cc.<\/p>\n\n<div class=\"code-block\">\/\/ useDebounce.ts\nexport function useDebounce&lt;T&gt;(value: T, delay = 500): T {\n  const [debounced, setDebounced] = useState(value);\n  useEffect(() =&gt; {\n    const id = setTimeout(() =&gt; setDebounced(value), delay);\n    return () =&gt; clearTimeout(id);  \/\/ value \uac00 \ub610 \ubc14\ub00c\uba74 \uc61b timer \ucde8\uc18c\n  }, [value, delay]);\n  return debounced;\n}\n\n\/\/ \uc0ac\uc6a9\nfunction Search() {\n  const [query, setQuery] = useState(&#8221;);\n  const debouncedQuery = useDebounce(query, 500);\n  const { data } = useFetch(`\/api\/search?q=${debouncedQuery}`);  \/\/ \u2190 \ub514\ubc14\uc6b4\uc2a4\ub41c \uac12\n  return &lt;input value={query} onChange={(e) =&gt; setQuery(e.target.value)} \/&gt;;\n}<\/div>\n\n<p>\uc774 3 Hook \ub9cc \uc190\uc5d0 \uc775\uc73c\uba74 React \ucf54\ub4dc\ub7c9\uc774 30% \uc904\uc5b4\ub4e0\ub2e4. \ud68c\uc0ac \ucf54\ub4dc\ubca0\uc774\uc2a4\uc5d0 <code>src\/hooks\/<\/code> \ud3f4\ub354 \ub9cc\ub4e4\uace0 \uac70\uae30\uc5d0 \ub204\uc801\ud558\ub294 \uac8c \ud45c\uc900 \ud328\ud134.<\/p>\n\n<div class=\"warnbox\">\n<strong>\uaf2d \uc9c1\uc811 \ub9cc\ub4e4 \ud544\uc694\ub294 \uc5c6\ub2e4<\/strong> \u2014 <strong>usehooks-ts<\/strong> \u00b7 <strong>react-use<\/strong> \uac19\uc740 \ub77c\uc774\ube0c\ub7ec\ub9ac\uac00 50+ \ucee4\uc2a4\ud140 Hook \uc744 \uac80\uc99d\ub41c \ud615\ud0dc\ub85c \uc81c\uacf5. \uc6b0\ub9ac stack \uc758 <code>useFetch<\/code> \ub294 TanStack Query \uac00 \ub354 \uac15\ub825 (17\ud3b8). \ub2e8, \ud559\uc2b5 \ub2e8\uacc4\uc5d0\uc120 \uc9c1\uc811 \ub9cc\ub4e4\uc5b4\ubd10\uc57c \uc5b4\ub5bb\uac8c \ub3d9\uc791\ud558\ub294\uc9c0 \uc548\ub2e4.\n<\/div>\n\n<p>12\ud3b8\uc73c\ub85c React \uc758 \ucef4\ud3ec\ub10c\ud2b8\u00b7Hook \uc0ac\uace0\ubc29\uc2dd\uc774 \uc644\uc131. 13\ud3b8\ubd80\ud130\ub294 \ucef4\ud3ec\ub10c\ud2b8 \uac04 \ub370\uc774\ud130 \uacf5\uc720 \u2014 prop drilling \uc744 \ub04a\ub294 <strong>Context API<\/strong> \ub85c.<\/p>\n\n<div class=\"cta\">\n<h3>\ub2e4\uc74c \uae00<\/h3>\n<p>React \uad50\uc7ac 13\ud3b8 \u2014 Context API. \uc804\uc5ed \uc0c1\ud0dc\u00b7prop drilling \ud0c8\ucd9c\u00b7useContext + Provider \ud328\ud134.<\/p>\n<\/div>\n\n<div class=\"footer-nav\">\nReact \uad50\uc7ac \uc2dc\ub9ac\uc988 \u00b7\n<a href=\"https:\/\/junai.ai\/blog\/react-forms-10\/\">10\ud3b8 Form<\/a> \u00b7\n<a href=\"https:\/\/junai.ai\/blog\/react-useeffect-11\/\">11\ud3b8 useEffect<\/a> \u00b7\n<strong>12\ud3b8 \ucee4\uc2a4\ud140 Hook<\/strong>\n<\/div>\n\n<\/article>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>React \ucee4\uc2a4\ud140 Hook \u2014 use- prefix\u00b7\ub85c\uc9c1 \ucd94\ucd9c\u00b7\uc7ac\uc0ac\uc6a9. useFetch\u00b7useLocalStorage\u00b7useDebounce \uc608\uc81c. \uad50\uc7ac 12\ud3b8.<\/p>\n","protected":false},"author":1,"featured_media":751,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22],"tags":[],"class_list":["post-776","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-react"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/posts\/776","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/comments?post=776"}],"version-history":[{"count":1,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/posts\/776\/revisions"}],"predecessor-version":[{"id":777,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/posts\/776\/revisions\/777"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/media\/751"}],"wp:attachment":[{"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/media?parent=776"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/categories?post=776"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/tags?post=776"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}