{"id":774,"date":"2026-05-18T23:01:44","date_gmt":"2026-05-18T15:01:44","guid":{"rendered":"https:\/\/junai.ai\/blog\/react-usereducer-14\/"},"modified":"2026-05-19T20:26:21","modified_gmt":"2026-05-19T12:26:21","slug":"react-usereducer-14","status":"publish","type":"post","link":"https:\/\/junai.ai\/blog\/react-usereducer-14\/","title":{"rendered":"React useReducer \u2014 \ubcf5\uc7a1 state \ub2e4\ub8e8\uae30 (Ch.14)"},"content":{"rendered":"\n<!-- WordPress REST API \ubc1c\ud589\uc6a9 HTML (\uc790\ub3d9 \uc0dd\uc131) -->\n<!-- WP-FEATURED-MEDIA-ID: 749 -->\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 14\ud3b8<\/span>\n  <h1>React useReducer \u2014 \ubcf5\uc7a1 state \ub2e4\ub8e8\uae30<\/h1>\n  <p>useState \uac00 5\uac1c \ub118\uae30 \uc2dc\uc791\ud558\uba74 useReducer. action \uc73c\ub85c \uc0c1\ud0dc \ubcc0\ud654\ub97c \ud55c \uacf3\uc5d0 \ubaa8\uc740\ub2e4.<\/p>\n  <img decoding=\"async\" src=\"https:\/\/junai.ai\/blog\/wp-content\/uploads\/2026\/05\/hero-5-32.jpg\" alt=\"state \ub178\ub4dc\ub4e4 \uc0ac\uc774 action \ud654\uc0b4\ud45c\uac00 \uc804\uc774\ub97c \ud45c\uc2dc\ud558\ub294 \ub2e4\uc774\uc5b4\uadf8\ub7a8 \uc77c\ub7ec\uc2a4\ud2b8 \u2014 useReducer \ucee8\uc149\">\n<\/section>\n\n<div class=\"container\">\n<article>\n\n<p>10\ud3b8 \ud68c\uc6d0\uac00\uc785 form \uc758 \uac1d\uccb4 useState \uac00 \uc88b\uc740 \ucd9c\ubc1c\uc810\uc774\uc5c8\ub2e4. \uadf8\ub7f0\ub370 \uac70\uae30\uc5d0 \uac80\uc99d \uc5d0\ub7ec\u00b7\ub85c\ub529\u00b7\uc5d0\ub7ec \uba54\uc2dc\uc9c0\u00b7\uc9c4\ud589 \ub2e8\uacc4\uae4c\uc9c0 \ucd94\uac00\ub418\uba74? <code>const [form, setForm] = useState({...})<\/code> \uac00 7~10 \ud544\ub4dc\uc758 \uac70\ub300\ud55c \uac1d\uccb4\uac00 \ub418\uace0, <code>setForm(prev =&gt; ({ ...prev, ... }))<\/code> \ub85c\uc9c1\uc774 \ucef4\ud3ec\ub10c\ud2b8 \uc548\uc5d0 \ud769\uc5b4\uc9c4\ub2e4. <strong>useReducer<\/strong> \uac00 \uc815\ub9ac\ud558\ub294 \uc790\ub9ac.<\/p>\n\n<p>\uc774\ubc88 14\ud3b8\uc740 useState \uc640 useReducer \uc758 \uc815\ud655\ud55c \ucc28\uc774 + reducer \ud328\ud134 \uc791\uc131\ubc95 + 13\ud3b8 Context \uc640\uc758 \uacb0\ud569 + &#8220;\uc5b8\uc81c \uac08\uc544\ud0c8\uc9c0&#8221; 4 \uc2e0\ud638.<\/p>\n\n<h2>1. useState \u2192 useReducer \ud55c \uc904 \ube44\uad50<\/h2>\n\n<div class=\"code-block\">\/\/ useState (10\ud3b8 form)\nconst [form, setForm] = useState({ email: &#8221;, password: &#8221; });\nconst handleChange = (e) =&gt; {\n  const { name, value } = e.target;\n  setForm(prev =&gt; ({ &#8230;prev, [name]: value }));\n};\n\n\/\/ useReducer\nconst [form, dispatch] = useReducer(formReducer, { email: &#8221;, password: &#8221; });\nconst handleChange = (e) =&gt; {\n  dispatch({ type: &#8216;FIELD_CHANGE&#8217;, field: e.target.name, value: e.target.value });\n};\n\nfunction formReducer(state, action) {\n  switch (action.type) {\n    case &#8216;FIELD_CHANGE&#8217;:\n      return { &#8230;state, [action.field]: action.value };\n    case &#8216;RESET&#8217;:\n      return { email: &#8221;, password: &#8221; };\n    default:\n      return state;\n  }\n}<\/div>\n\n<p>useReducer \uc758 \ud575\uc2ec \u2014 <strong>state \ubcc0\ud654 \ub85c\uc9c1 (\uc5b4\ub5bb\uac8c \ubc14\uafc0\uc9c0) \uc744 reducer \ud568\uc218 \ud55c \uacf3\uc5d0 \ubaa8\uc74c<\/strong>. \ucef4\ud3ec\ub10c\ud2b8\ub294 &#8220;\ubb34\uc5c7\uc744 \ud588\ub294\uc9c0&#8221; (action) \ub9cc dispatch \ud558\uba74 \ub428.<\/p>\n\n<h2>2. reducer \ud568\uc218\uc758 \uaddc\uce59<\/h2>\n\n<p>reducer \ub294 \ub2e8\uc21c \ud568\uc218 \u2014 <code>(\ud604\uc7ac state, action) =&gt; \uc0c8 state<\/code>. \ub2e4\ub9cc 3\uac00\uc9c0 \uaddc\uce59 \uc5c4\uc218.<\/p>\n\n<div class=\"databox\">\n<strong>\uaddc\uce59<\/strong> \u2014 \u2460 \uc21c\uc218 \ud568\uc218 (\uac19\uc740 \uc785\ub825 \u2192 \uac19\uc740 \ucd9c\ub825, side effect 0). \u2461 \uc0c8 state \uac1d\uccb4 \ubc18\ud658 (\uc9c1\uc811 \uc218\uc815 \u274c). \u2462 default \ucf00\uc774\uc2a4 \ud544\uc218 (action.type \uc774 \ub9e4\uce6d \uc548 \ub418\uba74 state \uadf8\ub300\ub85c). \uc774 \uc14b\uc774 Redux \uc758 \uc6d0\uce59 \uadf8\ub300\ub85c\ub2e4.\n<\/div>\n\n<p>action \uac1d\uccb4\uc758 \ud45c\uc900 \ud615\ud0dc:<\/p>\n\n<div class=\"code-block\">\/\/ \ucd5c\uc18c \ud615\ud0dc\n{ type: &#8216;INCREMENT&#8217; }\n\n\/\/ payload \ucd94\uac00\n{ type: &#8216;ADD_TODO&#8217;, text: &#8216;\uc6b0\uc720 \uc0ac\uae30&#8217;, id: crypto.randomUUID() }\n\n\/\/ TypeScript discriminated union (\uc2e4\uc804 \ud45c\uc900)\ntype Action =\n  | { type: &#8216;INCREMENT&#8217; }\n  | { type: &#8216;ADD_TODO&#8217;; text: string; id: string }\n  | { type: &#8216;DELETE_TODO&#8217;; id: string };<\/div>\n\n<p>TypeScript \uc758 discriminated union \uc73c\ub85c reducer \uc548 switch \uc758 \uac01 case \uc5d0\uc11c \uc790\ub3d9 \ud0c0\uc785 \uc881\ud788\uae30. <code>action.text<\/code> \uac00 <code>ADD_TODO<\/code> case \uc548\uc5d0\uc11c\ub9cc string \uc73c\ub85c \uc778\uc2dd\ub418\uc5b4 IDE \uac00 \uc7a1\uc544\uc900\ub2e4.<\/p>\n\n<h2>3. \uc5b8\uc81c \uac08\uc544\ud0c8\uae4c \u2014 4 \uc2e0\ud638<\/h2>\n\n<p>useState \uac00 \ucda9\ubd84\ud55c\uc9c0 useReducer \uac00 \ud544\uc694\ud55c\uc9c0 \ud310\ub2e8 \uae30\uc900. \ud558\ub098\ub77c\ub3c4 \ud574\ub2f9\ud558\uba74 useReducer \uace0\ub824.<\/p>\n\n<div class=\"code-block\">\/\/ \uc2e0\ud638 1 \u2014 state \uac00 \uac1d\uccb4\uc774\uace0 \ud544\ub4dc 4\uac1c \uc774\uc0c1\nconst [state, setState] = useState({\n  email: &#8221;, password: &#8221;, name: &#8221;, phone: &#8221;, address: &#8221;, terms: false\n});\n\n\/\/ \uc2e0\ud638 2 \u2014 \ud55c \ud578\ub4e4\ub7ec\uac00 \uc5ec\ub7ec \ud544\ub4dc\ub97c \ub3d9\uc2dc\uc5d0 \ubc14\uafc8\nsetState(prev =&gt; ({\n  &#8230;prev,\n  email: validated,\n  error: null,\n  loading: true,\n}));\n\n\/\/ \uc2e0\ud638 3 \u2014 \uac19\uc740 state \ubcc0\uacbd \ub85c\uc9c1\uc774 \uc5ec\ub7ec \ud578\ub4e4\ub7ec\uc5d0 \ubc18\ubcf5\n\/\/ (handleSubmit \u00b7 handleReset \u00b7 handleAutoFill \ubaa8\ub450 \ube44\uc2b7\ud55c spread)\n\n\/\/ \uc2e0\ud638 4 \u2014 \uac19\uc740 state \ub97c \ub450 \ucef4\ud3ec\ub10c\ud2b8\uc5d0\uc11c \ubcc0\uacbd\n\/\/ (Context + dispatch \uc870\ud569 \uc790\uc5f0\uc2a4\ub7ec\uc6c0)<\/div>\n\n<p>\uac70\uafb8\ub85c useState \uac00 \ub2f5\uc778 \uacbd\uc6b0 \u2014 \ub2e8\uc77c \uac12(boolean\u00b7string\u00b7number), \ubcc0\uacbd \ud328\ud134\uc774 \ub2e8\uc21c, \ucef4\ud3ec\ub10c\ud2b8 \ud55c \uacf3\uc5d0\uc11c\ub9cc \ubcc0\uacbd. \ubb34\ub9ac\ud574\uc11c reducer \uc4f0\uba74 \ucf54\ub4dc\ub7c9\ub9cc \ub298\uc5b4\ub0a8.<\/p>\n\n<h2>4. Context + useReducer \u2014 Redux \uc9c1\uc804 \ub2e8\uacc4<\/h2>\n\n<p>13\ud3b8\uc758 Context \ud328\ud134 + useReducer \uacb0\ud569. \uc791\uc740~\uc911\uac04 \uc571\uc5d0\uc120 Redux \ub3c4\uc785 \uc5c6\uc774 \ucda9\ubd84.<\/p>\n\n<div class=\"code-block\">\/\/ CartContext.tsx\ntype State = { items: CartItem[]; total: number };\ntype Action =\n  | { type: &#8216;ADD&#8217;; item: CartItem }\n  | { type: &#8216;REMOVE&#8217;; id: string }\n  | { type: &#8216;CLEAR&#8217; };\n\nfunction cartReducer(state: State, action: Action): State {\n  switch (action.type) {\n    case &#8216;ADD&#8217;:\n      return { items: [&#8230;state.items, action.item], total: state.total + action.item.price };\n    case &#8216;REMOVE&#8217;:\n      const removed = state.items.find(i =&gt; i.id === action.id);\n      return {\n        items: state.items.filter(i =&gt; i.id !== action.id),\n        total: state.total &#8211; (removed?.price || 0),\n      };\n    case &#8216;CLEAR&#8217;:\n      return { items: [], total: 0 };\n  }\n}\n\nconst StateContext = createContext&lt;State&gt;(null!);\nconst DispatchContext = createContext&lt;React.Dispatch&lt;Action&gt;&gt;(null!);\n\nexport function CartProvider({ children }) {\n  const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 });\n  return (\n    &lt;StateContext.Provider value={state}&gt;\n      &lt;DispatchContext.Provider value={dispatch}&gt;\n        {children}\n      &lt;\/DispatchContext.Provider&gt;\n    &lt;\/StateContext.Provider&gt;\n  );\n}\n\nexport const useCart = () =&gt; useContext(StateContext);\nexport const useCartDispatch = () =&gt; useContext(DispatchContext);<\/div>\n\n<p>State \uc640 Dispatch \ub97c \ub2e4\ub978 Context \ub85c \ubd84\ub9ac (13\ud3b8 \uc131\ub2a5 \ud568\uc815 \ud574\uacb0). \uce74\uc6b4\ud2b8\ub9cc \ubcf4\ub294 \ucef4\ud3ec\ub10c\ud2b8\ub294 useCart, add \ub9cc \ud558\ub294 \ucef4\ud3ec\ub10c\ud2b8\ub294 useCartDispatch. dispatch \ub294 \uc548 \ubc14\ub00c\ub2c8 \uc7ac\ub80c\ub354 0.<\/p>\n\n<div class=\"warnbox\">\n<strong>\uc5ec\uae30\uc11c Redux \uac00 \ud544\uc694\ud574\uc9c0\ub294 \uc9c0\uc810<\/strong> \u2014 middleware (logging\u00b7persistence\u00b7async), redux-devtools (time travel debugging), \ud070 \ud300\uc758 \ud45c\uc900\ud654. \uadf8 \ubbf8\ub9cc\uc774\uba74 Context + useReducer \uac00 \ucda9\ubd84. \ud55c \ubc88 Redux \ub85c \uac00\uba74 boilerplate \ube44\uc6a9\uc744 \uc601\uad6c\ud788 \uc9ca\uc5b4\uc9c4\ub2e4.\n<\/div>\n\n<p>14\ud3b8\uc73c\ub85c state \uad00\ub9ac\uc758 \ud070 \uadf8\ub9bc\uc774 \uc644\uc131. 15\ud3b8\ubd80\ud130\ub294 \uac19\uc740 React \uc571 \uc548\uc5d0\uc11c \uc5ec\ub7ec \ud398\uc774\uc9c0\ub97c \ub2e4\ub8e8\ub294 <strong>React Router<\/strong> \u2014 URL \u2194 \ucef4\ud3ec\ub10c\ud2b8 \ub9e4\ud551.<\/p>\n\n<div class=\"cta\">\n<h3>\ub2e4\uc74c \uae00<\/h3>\n<p>React \uad50\uc7ac 15\ud3b8 \u2014 React Router v7. SPA \ub77c\uc6b0\ud305, useNavigate, \ub3d9\uc801 \ud30c\ub77c\ubbf8\ud130, nested routes.<\/p>\n<\/div>\n\n<div class=\"footer-nav\">\nReact \uad50\uc7ac \uc2dc\ub9ac\uc988 \u00b7\n<a href=\"https:\/\/junai.ai\/blog\/react-custom-hooks-12\/\">12\ud3b8 Custom Hook<\/a> \u00b7\n<a href=\"https:\/\/junai.ai\/blog\/react-context-13\/\">13\ud3b8 Context<\/a> \u00b7\n<strong>14\ud3b8 useReducer<\/strong>\n<\/div>\n\n<\/article>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>React useReducer \u2014 state\u00b7action\u00b7reducer \ud328\ud134, useState \uc640 \ube44\uad50 4 \uc2e0\ud638, Context \uacb0\ud569. \uad50\uc7ac 14\ud3b8.<\/p>\n","protected":false},"author":1,"featured_media":749,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22],"tags":[],"class_list":["post-774","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\/774","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=774"}],"version-history":[{"count":1,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/posts\/774\/revisions"}],"predecessor-version":[{"id":802,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/posts\/774\/revisions\/802"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/media\/749"}],"wp:attachment":[{"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/media?parent=774"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/categories?post=774"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/junai.ai\/blog\/wp-json\/wp\/v2\/tags?post=774"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}