๐ŸŒ˜

5. useReducer

ย 

5.1 useReducer๋ž€?

์ปดํฌ๋„ŒํŠธ์˜ state๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด React์—์„œ๋Š” ๊ธฐ๋ณธ Hook์ธ useState์™€ useState๋กœ๋ถ€ํ„ฐ ํŒŒ์ƒ๋œ ์ถ”๊ฐ€์ ์ธ Hook์ธ useReducer๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘ Hook์€ ๊ฐ™์€ ์—ญํ• ์„ ํ•˜์ง€๋งŒ useState๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ƒํƒœ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค๋ฉด useReducer๋Š” action ๊ฐ์ฒด์™€ reducer ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ƒํƒœ์— ์ ‘๊ทผํ•œ๋‹ค๋Š” ์ฐจ์ด์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
useReducer๋ฅผ ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ทธ๋ฆผ์„ ํ†ตํ•ด useState์™€ useReducer์˜ ์ฐจ์ด๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 5-1๊ทธ๋ฆผ 5-1
๊ทธ๋ฆผ 5-1
ย 
์œ„ ๊ทธ๋ฆผ์€ useState๋ฅผ ์€ํ–‰์— ๋น—๋Œ€์–ด ์„ค๋ช…ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. 100์›์„ ์ž…๊ธˆํ•  ๊ฒƒ์ด๋ผ๋Š” ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž์™€ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ขŒ์ธ state๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ์ž…๊ธˆ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ง์ ‘ ๊ณ„์ขŒ์— ์ž…๊ธˆ ๋‚ด์—ญ์„ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ์ง์ ‘ state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด setState๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 5-2๊ทธ๋ฆผ 5-2
๊ทธ๋ฆผ 5-2
ย 
์œ„ ๊ทธ๋ฆผ์€ useReducer์˜ ํ๋ฆ„์„ ์€ํ–‰์œผ๋กœ ํ‘œํ˜„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” โ€˜์ž…๊ธˆ์„ ํ•  ๊ฒƒ์ด๋‹คโ€™๋ผ๋Š” ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, useState์™€ ๋‹ฌ๋ฆฌ ์ฐฝ๊ตฌ ์ง์›๊ณผ ์€ํ–‰ ์‹œ์Šคํ…œ์ด๋ผ๋Š” ์ค‘๊ฐ„์ž ์—ญํ• ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ, ์‚ฌ์šฉ์ž๋Š” โ€˜์ž…๊ธˆโ€™์ด๋ผ๋Š” action์„ ์ฐฝ๊ตฌ ์ง์›์—๊ฒŒ ๋ณด๋‚ด๋ฉด ์ด๋ฅผ ์€ํ–‰ ์‹œ์Šคํ…œ์— ์ „๋‹ฌํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ณ„์ขŒ์˜ state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
ย 
์ด๋ ‡๊ฒŒ ์‚ฌ์šฉ์ž์˜ ์š”๊ตฌ ์‚ฌํ•ญ์ด action์ด๋ฉฐ dispatch์˜ ์—ญํ• ์„ ํ•˜๋Š” ์ฐฝ๊ตฌ ์ง์›์ด reducer์˜ ์—ญํ• ์„ ํ•˜๋Š” ์€ํ–‰ ์‹œ์Šคํ…œ์— ์ „๋‹ฌํ•˜๋ฉด ๊ณ„์ขŒ์˜ state๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. useReducer๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ž…๊ธˆ์ด๋‚˜ ์ถœ๊ธˆ ๋“ฑ ๋‹ค์–‘ํ•œ ์ผ์„ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ  action์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณต์žกํ•œ state๋ฅผ ๋‹ค๋ฃจ์–ด์•ผ ํ•œ๋‹ค๋ฉด useReducer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 5-3๊ทธ๋ฆผ 5-3
๊ทธ๋ฆผ 5-3
ย 
useReducer์˜ ํ๋ฆ„์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด์„œ dispatch ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ action์„ ๋„ฃ์–ด์„œ reducer์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. reducer๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋„ฃ์€ action์— ๋งž์ถฐ state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
dispatch, action, reducer ์ •๋ฆฌ dispatch๋Š” state ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์š”๊ตฌ์ด๋ฉฐ, action์€ ์š”๊ตฌ์˜ ๋‚ด์šฉ, reducer๋Š” state๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์ฃผ๋Š” ์—ญํ• ์ด๋ฉฐ ์ปดํฌ๋„ŒํŠธ์˜ state๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด reducer๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ย 
๊ทธ๋ ‡๋‹ค๋ฉด useReducer์™€ useState๋ฅผ ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. useReducer๋Š” useState๋ณด๋‹ค ๋” ๋ณต์žกํ•œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. state๊ฐ€ ๋‹จ์ˆœํ•  ๊ฒฝ์šฐ์—๋Š” useReducer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ฝ”๋“œ๋ฅผ ๋” ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— useState๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด๊ฐ™์ด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ•˜์œ„ ๊ฐ’์„ ํฌํ•จํ•˜๋Š” ๋ณต์žกํ•œ state๋ฅผ ๊ฐ€์ง€๊ฑฐ๋‚˜ ํ™•์žฅ์„ฑ์ด ์žˆ์„ ๋•Œ๋Š” useReducer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ state๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค๋ฉด ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•ด์ฃผ๊ณ  ์œ ์ง€๋ณด์ˆ˜๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

5.1.1. useReducer ๊ธฐ๋ณธ ๊ตฌ์กฐ

const [state, dispatch] = useReducer(reducer, initialArg, init);
ย 
useReducer์˜ ๊ธฐ๋ณธ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค. state๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ, dispatch๋Š” reducer ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. useReducer์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์—์„œ state ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋Š” ํ•จ์ˆ˜์ธ reducer, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” state์˜ ๊ธฐ๋ณธ๊ฐ’, ์„ธ ๋ฒˆ์งธ ์ธ์ž๋Š” ์„ ํƒ์‚ฌํ•ญ์œผ๋กœ ์ดˆ๊ธฐํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
ย 
๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด useReducer์˜ ์‚ฌ์šฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; function App() { const [count, setCount] = useState(0); console.log(count); function down() { setCount(count - 1); console.log('์‚ฌ๊ณผ๋ฅผ 1๊ฐœ ๋จน์—ˆ์Šต๋‹ˆ๋‹ค.'); } function reset() { setCount(0); console.log('์‚ฌ๊ณผ๋ฅผ ๋ชจ๋‘ ๋จน์—ˆ์Šต๋‹ˆ๋‹ค.'); } function up() { setCount(count + 1); console.log('์‚ฌ๊ณผ๋ฅผ 1๊ฐœ ๊ตฌ๋งคํ–ˆ์Šต๋‹ˆ๋‹ค.'); } return ( <div> <p>ํ˜„์žฌ ๋‚˜์—๊ฒŒ ์žˆ๋Š” ์‚ฌ๊ณผ์˜ ๊ฐœ์ˆ˜๋Š” {count}๊ฐœ</p> <input type="button" value="๐Ÿ 1๊ฐœ ๋จน์Œ!" onClick={down}></input> <input type="button" value="๐ŸŽ 1๊ฐœ ๊ตฌ๋งค!" onClick={up}></input> <input type="button" value="๐Ÿฝ๏ธ ๋ชจ๋‘ ๋จน์Œ!" onClick={reset}></input> </div> ); } export default App;
App.jsx
ย 
๊ทธ๋ฆผ 5-4๊ทธ๋ฆผ 5-4
๊ทธ๋ฆผ 5-4
ย 
์šฐ๋ฆฌ์—๊ฒŒ ์ต์ˆ™ํ•œ useState๋กœ ์ฆ๊ฐ ๊ธฐ๋Šฅ๊ณผ ์ดˆ๊ธฐํ™” ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ์นด์šดํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น event์— ๋งž๊ฒŒ count๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉฐ ์ฝ˜์†”์— count ๊ฐ’์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
ย 
import { useReducer } from "react"; function reducer(prevCount, action) { // --- โ“ท if (action === "up") { return prevCount + 1; } else if (action === "down") { return prevCount - 1; } else if (action === "reset") { return 0; } } function App() { const [count, dispatch] = useReducer(reducer, 0); // --- โ“ต // count๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” dispatch๋ฅผ ์‚ฌ์šฉ function down() { // --- โ“ถ action ๊ฐ’ ์ „๋‹ฌ dispatch("down"); } function reset() { dispatch("reset"); } function up() { dispatch("up"); } return ( <div> <p>ํ˜„์žฌ ๋‚˜์—๊ฒŒ ์žˆ๋Š” ์‚ฌ๊ณผ์˜ ๊ฐœ์ˆ˜๋Š” {count}๊ฐœ</p> <input type="button" value="๐Ÿ 1๊ฐœ ๋จน์Œ!" onClick={down}></input> <input type="button" value="๐ŸŽ 1๊ฐœ ๊ตฌ๋งค!" onClick={up}></input> <input type="button" value="๐Ÿฝ๏ธ ๋ชจ๋‘ ๋จน์Œ!" onClick={reset}></input> </div> ); } export default App;
App.jsx
ย 
useState๋กœ ๊ตฌํ˜„ํ•œ ์นด์šดํ„ฐ๋ฅผ useReducer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ”๊ฟ”๋ณด์•˜์Šต๋‹ˆ๋‹ค. โ“ต์—์„œ useReducer๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. useReducer์˜ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์œ„ ์˜ˆ์‹œ์—์„œ count๋Š” 0์œผ๋กœ ์ดˆ๊ธฐํ™” ๋ฉ๋‹ˆ๋‹ค.
ย 
const [state, dispatch] = useReducer(reducer, ์ดˆ๊นƒ๊ฐ’);
ย 
โ“ถ์—์„œ ๊ฐ ๋ฒ„ํŠผ์˜ ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ onClick ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. onClick ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด dispatch๋Š” action ๊ฐ’์„ ๋‹ด์•„ state์™€ ํ•จ๊ป˜ โ“ท์œผ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. reducer์—์„œ๋Š” ์ „๋‹ฌ๋ฐ›์€ action๊ณผ ์ผ์น˜ํ•˜๋Š” ๊ฐ’์„ ์ฐพ๋Š” ์กฐ๊ฑด๋ฌธ์„ ์‹คํ–‰ํ•˜์—ฌ ์ƒˆ๋กœ์šด state๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
ย 
useState๋ฅผ ์‚ฌ์šฉํ•œ ์นด์šดํ„ฐ์—์„œ๋Š” setCount๋กœ ์ง์ ‘ state๋ฅผ ๋ณ€๊ฒฝํ•˜์˜€์ง€๋งŒ, useReducer์—์„œ๋Š” setState์˜ ๊ธฐ๋Šฅ์„ ์„ธ๋ถ„ํ™”ํ•˜์—ฌ ์ง์ ‘ state์— ์ ‘๊ทผํ•˜์ง€ ์•Š์œผ๋ฉฐ state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ reducer ํ•จ์ˆ˜์—์„œ ์ง‘์ค‘์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด reducer ํ•จ์ˆ˜ ๋‚ด๋ถ€์— state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ณผ์ •์„ ์€๋‹‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด๊ฒƒ์ด useReducer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ  ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.
ย 

5.2 reducer ํ•จ์ˆ˜๋ž€?

reducer ํ•จ์ˆ˜๋Š” ํ˜„์žฌ state์™€ action ๊ฐ’์„ ์ „๋‹ฌ๋ฐ›์•„ ์ƒˆ๋กœ์šด state๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
ย 
// ํ˜„์žฌ ์ƒํƒœ(state)์™€ ํ–‰๋™(action)์„ ์ธ์ž๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค. // ํ•จ์ˆ˜ ์„ ์–ธ์‹ function Reducer(state, action) => { return { ... } } // ๋˜๋Š” // ํ•จ์ˆ˜ ํ‘œํ˜„์‹ const Reducer = (state, action) => { return { ... } }
ย 

5.2.1 action๊ณผ type

action์€ ํ˜„์žฌ state์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋‹ด์€ ๊ฐ’์œผ๋กœ, ๋ณดํ†ต ๊ฐ์ฒด์˜ ํ˜•ํƒœ๋ฅผ ๋ฑ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, action์˜ ๊ฐ’์€ ๋ฌธ์ž์—ด์ด๋‚˜ ์ˆซ์ž์—ฌ๋„ ์ƒ๊ด€์ด ์—†์Šต๋‹ˆ๋‹ค.
reducer์˜ ๋กœ์ง์€ action์ด ์ƒˆ๋กœ์šด state๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ ์“ฐ์ด๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด action.type์„ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ, action๋“ค์ด ๋ถ„๋ช…ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๊ณ  ์œ ์šฉํ•œ ์ •๋ณด๋ฅผ ์ฃผ๋„๋ก ์„ค๋ช…ํ•˜๋Š” ๋ฐฉ์‹์˜ type ํ•„๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, useReducer์—์„œ ์‚ฌ์šฉํ•˜๋Š” action ๊ฐ์ฒด์— type ํ•„๋“œ๋ฅผ ๋ช…์‹œํ•˜๋Š” ๊ฒƒ ๋˜ํ•œ ์ž์œ ์ž…๋‹ˆ๋‹ค.
ย 
// ์•„๋ž˜ ์ฝ”๋“œ๋Š” 5.3์—์„œ ๋‹ค๋ฃฐ ์ฝ”๋“œ์—์„œ ๋ฐœ์ทŒํ•˜์˜€์Šต๋‹ˆ๋‹ค. switch (action.type) { // Reducer ํ•จ์ˆ˜๋กœ ์ „๋‹ฌ๋œ action ๊ฐ์ฒด์˜ ๊ฐ’์ด // { type: "LOGIN_SUCCESS" } ๋ผ๋ฉด ์•„๋ž˜์˜ ๊ตฌ๋ฌธ๋“ค์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. case "LOGIN_SUCCESS": return { ...state, user: action.payload, isLogin: true, message: "๋กœ๊ทธ์ธ ์„ฑ๊ณต!", }; . . . }
ย 
์œ„์˜ ์ฝ”๋“œ๋Š” switch๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ action ๊ฐ์ฒด์˜ type ํ•„๋“œ์— ๋งคํ•‘๋˜์–ด ์žˆ๋Š” ๊ฐ’, ์ฆ‰ โ€œLOGIN_SUCCESSโ€ case์— ๋”ฐ๋ผ state๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
ย 

5.2.2 reducer ํ•จ์ˆ˜์˜ ์กฐ๊ฑด - ์ˆœ์ˆ˜ ํ•จ์ˆ˜

reducer๋ฅผ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋กœ ์ž‘์„ฑํ•˜๋Š” ์ด์œ ๋Š” ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์— ๋ชฉ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ถ€๊ฐ€์ ์œผ๋กœ, Redux์˜ ๋ณ€๊ฒฝ ๊ฐ์ง€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ์˜ํ•ด์„œ๋„ reducer๋ฅผ ๋ฐ˜๋“œ์‹œ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ, reducer ํ•จ์ˆ˜ ๋‚ด์—์„œ reducer ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ ๋ฐ”๊นฅ์˜ ๋ณ€์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•ด์„œ๋Š” ์•ˆ๋ฉ๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ ๋กœ์ง์ธ AJAX ํ˜ธ์ถœ, Promise ๊ฐ์ฒด ์‚ฌ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ์ถ”๊ฐ€์ ์œผ๋กœ Date.now(), Math.random()๊ณผ ๊ฐ™์ด ๋ฌด์ž‘์œ„ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
Redux ๊ณต์‹ ๋ฌธ์„œ์—์„œ reducer ํ•จ์ˆ˜๊ฐ€ ์ˆœ์ˆ˜ ํ•จ์ˆ˜์—ฌ์•ผ ํ•˜๋Š” ์ด์œ ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
์ˆœ์ˆ˜ ํ•จ์ˆ˜(pure function) VS ๋น„์ˆœ์ˆ˜ ํ•จ์ˆ˜ (impure function) ๋™์ผํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ฃผ์–ด์ง€๋ฉด ํ•ญ์ƒ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ผ์ปซ์Šต๋‹ˆ๋‹ค. ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ€์ˆ˜ ํšจ๊ณผ(side effect)๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// ์ˆœ์ˆ˜ ํ•จ์ˆ˜, pure function function pureAdd(num1, num2) { return num1 + num2; } console.log(add(1, 2)); // 3 console.log(add(1, 2)); // 3
ย 
pureAdd ํ•จ์ˆ˜๋Š” ์™ธ๋ถ€ ํ™˜๊ฒฝ์— ์˜ํ•œ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๊ณ , ์ฃผ์–ด์ง„ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๊ฐ™์„ ๋•Œ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
let sideNumber = 10; // ๋น„์ˆœ์ˆ˜ ํ•จ์ˆ˜, impure function function impureAdd(num1, num2) { return num1 + num2 + sideNumber; } console.log(add(1, 2)); // 13 sideNumber = 20; console.log(add(1, 2)); // 23
ย 
impureAdd ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ ์™ธ๋ถ€์—์„œ ์„ ์–ธ๋œ sideNumber ๋ณ€์ˆ˜๊ฐ€ impureAdd ํ•จ์ˆ˜์—์„œ ๋‹ค๋ค„์ง€๊ณ  ์žˆ๊ณ , ์ฃผ์–ด์ง„ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๊ฐ™์•„๋„ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋น„์ˆœ์ˆ˜ ํ•จ์ˆ˜๋ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

5.2.3 reducer ํ•จ์ˆ˜์˜ ์กฐ๊ฑด - ์ƒํƒœ ๋ณ€์ด(state mutation) ์ง€์–‘

state๋Š” ๋ถˆ๋ณ€์˜ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋ ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋นˆ๋ฒˆํžˆ state์˜ ์›๋ณธ์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€, ํ˜น์€ ๋ฎ์–ด์“ฐ๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ state ๋ณ€์ด๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค. ๋จผ์ €, ๊ธฐ์กด์˜ state๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ๋ฆฌ๋ Œ๋”๋ง ์‹œ์— ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋””๋ฒ„๊ทธํ•˜๋”๋ผ๋„, state๊ฐ€ ์–ด๋””์„œ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์•Œ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ๊นŠ์€ ๋ณต์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—…๋ฐ์ดํŠธํ•œ state๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๊ธฐ์กด state๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” Object.assign() ๋˜๋Š” spread ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊นŠ์€ ๋ณต์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด state์˜ ๋ถˆ๋ณ€์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
๊นŠ์€ ๋ณต์‚ฌ(deep copy)? ๋ฐ์ดํ„ฐ(๊ฐ’) ์ž์ฒด๋ฅผ ๋ณต์‚ฌํ•˜์—ฌ ๋‹ค๋ฅธ ์ฃผ์†Œ๊ฐ’์„ ๊ฐ€์ง€๋Š” ์ƒˆ๋กœ์šด ๋ณ€์ˆ˜(๋˜๋Š” ๊ฐ์ฒด)๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ์ผ์ปซ์Šต๋‹ˆ๋‹ค.
ย 
function Reducer(state, action) { switch (action.type) { case "LOGIN_SUCCESS": // โŒ ์•„๋ž˜์™€ ๊ฐ™์€ ์ƒํƒœ ๋ณ€์ด๋ฅผ ์ง€์–‘ state[user] = action.payload; state[isLogin] = true; state[message] = "๋กœ๊ทธ์ธ ์„ฑ๊ณต!"; return state; . . . }
ย 
function Reducer(state, action) { switch (action.type) { case "LOGIN_SUCCESS": // โœ… ๊นŠ์€ ๋ณต์‚ฌ๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ return { ...state, user: action.payload, isLogin: true, message: "๋กœ๊ทธ์ธ ์„ฑ๊ณต!", }; . . . }
ย 

5.3 useReducer ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

์•ž์„œ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋“ค์„ ํ†ตํ•˜์—ฌ useReducer์™€ reducer ํ•จ์ˆ˜์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด์ œ ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ์ด์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ฐ™์ด ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

5.3.1 ์˜ˆ์ œ ์„ค๋ช…

์•„๋ž˜์˜ ์˜ˆ์ œ๋Š” 2์žฅ useState์—์„œ ์‚ดํŽด๋ณธ โ€œuseState์™€ ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ ๋กœ๊ทธ์ธ ํผโ€ ์˜ˆ์ œ๋ฅผ ์กฐ๊ธˆ ์‘์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  useState๋กœ ๊ตฌํ˜„๋œ ์ด ๊ธฐ๋Šฅ์„ useReducer๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์—ฐ์Šต์„ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

5.3.2 src ํด๋” ๊ตฌ์กฐ

src ํด๋”์˜ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
(context์™€ reducer ํด๋”๋Š” useReducer๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.)
ย 
src โ”œโ”€ app.css โ”œโ”€ App.jsx โ”œโ”€ index.jsx โ”‚ โ”œโ”€ components โ”‚โ””โ”€ LoginForm.jsx โ”‚ โ”œโ”€ context โ”‚โ””โ”€ Context.jsx โ”‚ โ””โ”€ reducer โ””โ”€ Reducer.jsx
ย 

5.3.3 useState๋กœ ๊ตฌํ˜„๋œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ

์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” 2์žฅ useState์—์„œ ์‚ดํŽด๋ณธ ๋กœ๊ทธ์ธ ํผ ์˜ˆ์ œ๋ฅผ ์•ฝ๊ฐ„ ์ˆ˜์ •ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ useState๋กœ ๊ตฌํ˜„๋œ ๋กœ๊ทธ์ธ ํผ์˜ ๋™์ž‘ ๋‚ด์šฉ์„ ๊ฐ„๋‹จํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; const container = document.getElementById("root"); const root = createRoot(container); root.render(<App />);
index.jsx
ย 
์•„๋ž˜์˜ App ์ปดํฌ๋„ŒํŠธ๋Š” isLogin์ด๋ผ๋Š” ๋กœ๊ทธ์ธ ์ƒํƒœ๊ฐ€ ๊ฐ€์ง„ ๊ฐ’์— ๋”ฐ๋ผ true์ด๋ฉด โ€œํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค~ ๋ผ์ด์บฃ๋‹˜!โ€ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚˜๊ณ , false์ด๋ฉด ๋กœ๊ทธ์ธ ํผ์ด ๋‚˜ํƒ€๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  isLogin์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” setIsLogin ํ•จ์ˆ˜๋ฅผ LoginForm ์ปดํฌ๋„ŒํŠธ์— props๋กœ ์ „๋‹ฌํ•˜์—ฌ LoginForm ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ isLogin์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; import LoginForm from "./components/LoginForm"; function App() { const [isLogin, setIsLogin] = useState(false); return ( <div> {isLogin ? ( <div> <strong>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค~ ๋ผ์ด์บฃ๋‹˜!</strong> <img src="https://paullab.co.kr/images/message_licat.png" alt="๋ผ์ด์บฃ" /> <button onClick={() => setIsLogin(!isLogin)}>๋กœ๊ทธ์•„์›ƒ</button> </div> ) : ( <LoginForm setIsLogin={setIsLogin} /> )} </div> ); } export default App;
App.jsx, ์ด๋ฏธ์ง€์˜ ์ถœ์ฒ˜ : ์ฃผ์‹ํšŒ์‚ฌ ์œ„๋‹ˆ๋ธŒ(https://paullab.co.kr/weniv.html)
ย 
์•„๋ž˜์˜ LoginForm ์ปดํฌ๋„ŒํŠธ๋Š” id์™€ password, ๊ทธ๋ฆฌ๊ณ  message๋ฅผ useState๋กœ ์„ ์–ธํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” form ์š”์†Œ ์•ˆ์— ์žˆ๋Š” input ์š”์†Œ๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์ „๋‹ฌ ๋ฐ›๊ณ  ์žˆ์œผ๋ฉฐ, setId, setPassword๋ฅผ ํ†ตํ•ด id์™€ password ๊ฐ’์„ ๊ฐฑ์‹  ๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
์‚ฌ์šฉ์ž๊ฐ€ input ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ž…๋ ฅํ•œ ๋’ค, โ€œ๋กœ๊ทธ์ธ ํ•˜๊ธฐโ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด handleLoginForm ํ•จ์ˆ˜๊ฐ€ ๋™์ž‘ํ•˜๋ฉฐ id์™€ password๊ฐ€ ๋ชจ๋‘ ์ผ์น˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ, App ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ ๋ฐ›์€ setIsLogin ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜์—ฌ isLogin์˜ ๊ฐ’์„ true๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์ค๋‹ˆ๋‹ค. ๊ทธ ์ด์™ธ์˜ ๊ฒฝ์šฐ์—๋Š” setMessage ํ•จ์ˆ˜๋ฅผ ํ†ตํ•˜์—ฌ โ€œ๋กœ๊ทธ์ธ ์‹คํŒจ!โ€๋ผ๋Š” ๋ฌธ๊ตฌ๋ฅผ โ€œ๋กœ๊ทธ์ธ ํ•˜๊ธฐโ€ ๋ฒ„ํŠผ ์•„๋ž˜์— ํ‘œ๊ธฐํ•˜์—ฌ ์ค๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; function LoginForm({ setIsLogin }) { const [id, setId] = useState(""); const [password, setPassword] = useState(""); const [message, setMessage] = useState(""); const handleLoginForm = (event) => { event.preventDefault(); if (id === "licat" && password === "weniv!!") { setIsLogin(true); setMessage("๋กœ๊ทธ์ธ ์„ฑ๊ณต!"); } else { setMessage("๋กœ๊ทธ์ธ ์‹คํŒจ!"); } }; return ( <form action="" onSubmit={handleLoginForm}> <label>ID </label> <input type="text" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setId(event.target.value)} /> <br /> <br /> <label>Password </label> <input type="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setPassword(event.target.value)} /> <br /> <br /> <button>๋กœ๊ทธ์ธ ํ•˜๊ธฐ</button> <br /> <p>{message}</p> </form> ); } export default LoginForm;
LoginForm.jsx
ย 
.main { margin-top: 30px; text-align: center; } img { display: block; width: 400px; margin: 0 auto; height: 400px; }
app.css
ย 
์•ž์—์„œ useState๋ฅผ ์ด์šฉํ•œ ๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๋‚ด์šฉ์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์กฐ๊ธˆ ๋” ์„ธ๋ถ€์ ์œผ๋กœ ์‚ดํŽด๋ณด๋ฉด ๋กœ๊ทธ์ธ ๊ณผ์ •์€ ์ด 4๊ฐ€์ง€ ๊ฒฝ์šฐ์˜ ์ˆ˜๋กœ ๋‚˜๋ˆ„์–ด ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
  1. id์™€ password๊ฐ€ ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ โ‡’ ๋กœ๊ทธ์ธ ์„ฑ๊ณต
  1. id๋งŒ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ โ‡’ ๋กœ๊ทธ์ธ ์‹คํŒจ
  1. password๋งŒ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ โ‡’ ๋กœ๊ทธ์ธ ์‹คํŒจ
  1. id์™€ password ๋ชจ๋‘ ๋ถˆ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ โ‡’ ๋กœ๊ทธ์ธ ์‹คํŒจ
ย 
id์™€ password ์ค‘ ์ ์–ด๋„ ํ•˜๋‚˜๋งŒ ๋ถˆ์ผ์น˜ํ•˜์—ฌ๋„ ๋กœ๊ทธ์ธ์€ ์‹คํŒจํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์šฐ๋ฆฌ๋Š” ์กฐ๊ธˆ ๋” ๊ฐ๊ฐ์˜ ์ƒํƒœ๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ๊ฐ๊ฐ์˜ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด ์„œ๋กœ ๋‹ค๋ฅธ message๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ฃผ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
์•„๋ž˜๋Š” ์•ž์„œ ์‚ดํŽด๋ณธ LoginForm ์ปดํฌ๋„ŒํŠธ์˜ handleLoginForm ํ•จ์ˆ˜์—์„œ ์กฐ๊ฑด๋ฌธ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋กœ๊ทธ์ธ ๊ณผ์ •์„ ์กฐ๊ธˆ ๋” ์„ธ๋ถ„ํ™”ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; function LoginForm({ setIsLogin }) { const [id, setId] = useState(""); const [password, setPassword] = useState(""); const [message, setMessage] = useState(""); const handleLoginForm = (event) => { event.preventDefault(); if (id === "licat" && password === "weniv!!") { setIsLogin(true); setMessage("๋กœ๊ทธ์ธ ์„ฑ๊ณต!"); } else if (id === "licat" && password !== "weniv!!") { setMessage("๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ธฐ์–ตํ•ด๋ณด์„ธ์š”~"); } else if (id !== "licat" && password === "weniv!!") { setMessage("์•„์ด๋””๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ธฐ์–ตํ•ด๋ณด์„ธ์š”~"); } else { setMessage("์•„์ด๋””๋ž‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ชจ๋‘ ํ‹€๋ ธ์–ด์š”~ ใ… ใ… "); } }; return ( <form action="" onSubmit={handleLoginForm}> <label>ID </label> <input type="text" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setId(event.target.value)} /> <br /> <br /> <label>Password </label> <input type="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setPassword(event.target.value)} /> <br /> <br /> <button>๋กœ๊ทธ์ธ ํ•˜๊ธฐ</button> <br /> <p>{message}</p> </form> ); } export default LoginForm;
LoginForm.jsx
ย 

5.3.4 useReducer๋กœ ๊ตฌํ˜„ํ•œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ

5.1๊ณผ 5.2์„ ํ†ตํ•˜์—ฌ ์•Œ๊ฒŒ ๋œ ๊ฒƒ๋“ค์„ ํ† ๋Œ€๋กœ ์•ž์„  useState๋กœ ๊ตฌํ˜„ํ•œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ useReducer๋กœ ๋ฐ”๊ฟ”๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
์œ„์—์„œ App, LoginForm์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๊ฐ๊ฐ์˜ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜์˜€์ง€๋งŒ, ์ฝ”๋“œ ๊ตฌํ˜„์‚ฌํ•ญ์„ ๋ช…ํ™•ํžˆ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ์ž ์‹œ App.jsx ํŒŒ์ผ ์•ˆ์— ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด์šฉ์„ ๋„ฃ์–ด ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
๊ฐ€์žฅ ๋จผ์ € ์‚ดํŽด๋ณผ ์‚ฌํ•ญ์€ App ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. App ์ปดํฌ๋„ŒํŠธ๋Š” LoginForm์„ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์•ž์„œ ์‚ดํŽด๋ณธ LoginForm์˜ ์ต์ˆ™ํ•œ ์ฝ”๋“œ๊ฐ€ ๋ณด์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋‹ฌ๋ผ์ง„ ์ ์€ userInfo๋ผ๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ๋‹ด๊ธด ๊ฐ์ฒด์™€ useReducer Hook์ด ์ด์šฉ๋œ ์ , ๊ทธ๋ฆฌ๊ณ  handleLoginForm์˜ ์กฐ๊ฑด๋ฌธ์— dispatch๊ฐ€ ์ถ”๊ฐ€๋œ ์ ์ž…๋‹ˆ๋‹ค. ์ด์ œ ๊ฐ๊ฐ ์ถ”๊ฐ€๋œ ์ƒˆ๋กœ์šด ๋ถ€๋ถ„๋“ค์„ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
๋จผ์ € userInfo๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด์ธ id์™€ password๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต์€ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค์ง€๋งŒ, ์šฐ๋ฆฌ์˜ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์—์„œ๋Š” ๋ฐ›์•„์™”๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์„ ์–ธํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
ย 
๋‹ค์Œ์œผ๋กœ useReducer Hook์€ ๋‚˜์ค‘์— ์‚ดํŽด๋ณผ reducer ํ•จ์ˆ˜๋ฅผ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. ์ดˆ๊ธฐ ์ƒํƒœ์—์„œ isLogin์€ false, ํ‘œ๊ธฐ๋  message๋Š” ๋นˆ ๋ฌธ์ž์—ด๋กœ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ƒํƒœ(state)์™€ dispatch๋ผ๋Š” ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜๋กœ useState์™€ ๊ฐ™์ด ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น ๋ฌธ๋ฒ•์„ ์ด์šฉํ•˜์—ฌ useReducer Hook์„ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.
ย 
๋งˆ์ง€๋ง‰์œผ๋กœ handleLoginForm ํ•จ์ˆ˜์˜ ์กฐ๊ฑด๋ฌธ์—์„œ๋Š” input ์š”์†Œ๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ ๋ฐ›์€ id์™€ password๊ฐ€ userInfo์˜ id์™€ password์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜์—ฌ ๊ฐ๊ฐ์˜ ๊ฒฝ์šฐ์— ๋งž์ถ”์–ด dispatch๋ฅผ ์ด์šฉํ•˜์—ฌ reducer ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•ด์•ผํ•  ๊ฐ’์„ action์˜ type์œผ๋กœ reducer ํ•จ์ˆ˜์—๊ฒŒ ์ „๋‹ฌํ•˜์—ฌ ์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์ถ”๊ฐ€๋กœ, ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ(type์ด โ€œLOGIN_SUCCESSโ€ ์ธ ๊ฒฝ์šฐ)๋ฅผ ์‚ดํŽด๋ณด๋ฉด dispatch ํ•จ์ˆ˜์— payload๋ผ๋Š” ๊ฐ’์„ type ์ด์™ธ์— ์ถ”๊ฐ€๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ฃผ๊ณ  ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ payload๋Š” action๊ณผ ๊ฐ™์ด ์ „๋‹ฌํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฐ’ ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” ๊ทธ๋ฆ‡๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
ย 
์ด์ œ reducer ํ•จ์ˆ˜์˜ ๋‚ด์šฉ์„ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. reducer ํ•จ์ˆ˜๋Š” ์ƒํƒœ(state)์™€ ํ–‰๋™(action)์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ ๋ฐ›์•„ action์˜ type์— ๋”ฐ๋ผ state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ๋ฐ˜ํ™˜์‹œ์ผœ์ฃผ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” switch ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ƒํƒœ ๋ฐ ๋กœ๊ทธ์ธํ•˜๊ธฐ ์œ„ํ•œ ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ๋ชจ๋‘ action์˜ type์œผ๋กœ ๋ฐ›์•„ ๊ตฌ๋ถ„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
ย 
import { useState, useReducer } from "react"; import "./app.css"; const reducer = (state, action) => { switch (action.type) { case "LOGIN_SUCCESS": return { ...state, user: action.payload, isLogin: true, message: "๋กœ๊ทธ์ธ ์„ฑ๊ณต!", }; case "MISS_ID": return { ...state, isLogin: false, message: "์•„์ด๋””๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ธฐ์–ตํ•ด๋ณด์„ธ์š”~", }; case "MISS_PASSWORD": return { ...state, isLogin: false, message: "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ธฐ์–ตํ•ด๋ณด์„ธ์š”~", }; case "LOGIN_FAILURE": return { ...state, isLogin: false, message: "์•„์ด๋””๋ž‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ชจ๋‘ ํ‹€๋ ธ์–ด์š”~ ใ… ใ… ", }; case "LOGOUT": return { ...state, isLogin: false, message: "๋กœ๊ทธ์•„์›ƒ!", }; default: return { ...state }; } }; function App() { const [id, setId] = useState(''); const [password, setPassword] = useState(''); const userInfo = { id: "licat", password: "weniv!!" }; const [state, dispatch] = useReducer(reducer, { isLogin: false, message: "" }); const handleLoginForm = (event) => { event.preventDefault(); if (id === userInfo.id && password === userInfo.password) { dispatch({ type: "LOGIN_SUCCESS", payload: userInfo }); } else if (id !== userInfo.id && password === userInfo.password) { dispatch({ type: "MISS_ID" }); } else if (id === userInfo.id && password !== userInfo.password) { dispatch({ type: "MISS_PASSWORD" }); } else { dispatch({ type: "LOGIN_FAILURE" }); } }; return ( <div className="main"> {state.isLogin ? ( <> <strong>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค~ ๋ผ์ด์บฃ๋‹˜!</strong> <img src="https://paullab.co.kr/images/message_licat.png" alt="๋ผ์ด์บฃ" /> <button onClick={() => dispatch({ type: "LOGOUT" })}>๋กœ๊ทธ์•„์›ƒ</button> </> ) : ( <form action="" onSubmit={handleLoginForm}> <label>ID</label> <input type="text" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setId(event.target.value)} /> <br /> <br /> <label>Password</label> <input type="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setPassword(event.target.value)} /> <br /> <br /> <button>๋กœ๊ทธ์ธ ํ•˜๊ธฐ</button> <br /> <p>{state.message}</p> </form> )} </div> ); } export default App;
App.jsx
ย 
์ด์ œ ์œ„์˜ ์ฝ”๋“œ๊ฐ€ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๊ฐ๊ฐ์˜ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด์„ ์บก์ณํ•œ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.(์ฐธ๊ณ : ์ž…๋ ฅ๋‚ด์šฉ์„ ๋ณด์ด๊ธฐ ์œ„ํ•ด password ์ž…๋ ฅ์ฐฝ์ธ input ์š”์†Œ์˜ type์„ text๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.)
ย 
  1. ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ (โ€LOGIN_SUCCESSโ€)
๊ทธ๋ฆผ 5-5๊ทธ๋ฆผ 5-5
๊ทธ๋ฆผ 5-5
๊ทธ๋ฆผ 5-6 [์ถœ์ฒ˜] https://paullab.co.kr/weniv.html๊ทธ๋ฆผ 5-6 [์ถœ์ฒ˜] https://paullab.co.kr/weniv.html
๊ทธ๋ฆผ 5-6 [์ถœ์ฒ˜] https://paullab.co.kr/weniv.html
  1. id๊ฐ€ ํ‹€๋ฆฐ ๊ฒฝ์šฐ ("MISS_ID")
๊ทธ๋ฆผ 5-7๊ทธ๋ฆผ 5-7
๊ทธ๋ฆผ 5-7
ย 
  1. password๊ฐ€ ํ‹€๋ฆฐ ๊ฒฝ์šฐ ("MISS_PASSWORD")
๊ทธ๋ฆผ 5-8๊ทธ๋ฆผ 5-8
๊ทธ๋ฆผ 5-8
  1. id์™€ password๊ฐ€ ๋ชจ๋‘ ํ‹€๋ฆฐ ๊ฒฝ์šฐ ("LOGIN_FAILURE")
๊ทธ๋ฆผ 5-9๊ทธ๋ฆผ 5-9
๊ทธ๋ฆผ 5-9
ย 
  1. ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ๊ฒฝ์šฐ ("LOGOUT")
๊ทธ๋ฆผ 5-10๊ทธ๋ฆผ 5-10
๊ทธ๋ฆผ 5-10
ย 

5.3.5 ์ปดํฌ๋„ŒํŠธ์˜ ๋ถ„๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  props drilling

์•ž์„œ 5.3.4์—์„œ๋Š” useReducer๋กœ์˜ ๋ณ€ํ™˜ ๊ณผ์ •๊ณผ ๊ทธ ํ๋ฆ„์„ ๋ช…ํ™•ํžˆ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ App.jsx ํ•˜๋‚˜์˜ ํŒŒ์ผ์— ๋ชจ๋“  ๋‚ด์šฉ์ด ์ž‘์„ฑ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ •์—์„œ๋Š” ๋‹ค์–‘ํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์„œ๋กœ ๋ถ„๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋กœ ์ธํ•ด useState์—์„œ ๋งˆ์ฃผํ•˜์˜€๋˜, props drilling ํ˜„์ƒ์„ ๋‹ค์‹œ ๋งˆ์ฃผํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” useReducer ์ด์šฉ ์‹œ, ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ state์™€ dispatch๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด ๋ถˆ๊ฐ€ํ”ผํ•˜๊ฒŒ ๋‚˜ํƒ€๋‚˜๋Š” ์ผ์ž…๋‹ˆ๋‹ค.
ย 
์•„๋ž˜๋Š” App.jsx์—์„œ ์ž‘์„ฑํ•˜์˜€๋˜ 5.3.4์˜ ์ฝ”๋“œ๋ฅผ ์ปดํฌ๋„ŒํŠธ ๋ณ„๋กœ ๋ถ„๋ฆฌํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ย 
const Reducer = (state, action) => { switch (action.type) { case "LOGIN_SUCCESS": return { ...state, user: action.payload, isLogin: true, message: "๋กœ๊ทธ์ธ ์„ฑ๊ณต!", }; case "MISS_ID": return { ...state, isLogin: false, message: "์•„์ด๋””๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ธฐ์–ตํ•ด๋ณด์„ธ์š”~", }; case "MISS_PASSWORD": return { ...state, isLogin: false, message: "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ธฐ์–ตํ•ด๋ณด์„ธ์š”~", }; case "LOGIN_FAILURE": return { ...state, isLogin: false, message: "์•„์ด๋””๋ž‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ชจ๋‘ ํ‹€๋ ธ์–ด์š”~ ใ… ใ… ", }; case "LOGOUT": return { ...state, isLogin: false, message: "๋กœ๊ทธ์•„์›ƒ!", }; default: return { ...state }; } }; export default Reducer;
Reducer.jsx
ย 
import { useReducer } from "react"; import Reducer from "./reducer/Reducer"; import LoginForm from "./components/LoginForm"; import "./styles.css"; function App() { const [state, dispatch] = useReducer(Reducer, { isLogin: false, message: "" }); return ( <div className="main"> {state.isLogin ? ( <> <strong>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค~ ๋ผ์ด์บฃ๋‹˜!</strong> <img src="https://paullab.co.kr/images/message_licat.png" alt="๋ผ์ด์บฃ" /> <button onClick={() => dispatch({ type: "LOGOUT" })}>๋กœ๊ทธ์•„์›ƒ</button> </> ) : ( <LoginForm state={state} dispatch={dispatch} /> )} </div> ); } export default App;
App.jsx
ย 
import { useState } from "react"; function LoginForm({ state, dispatch }) { const [id, setId] = useState(''); const [password, setPassword] = useState(''); const userInfo = { id: "licat", password: "weniv!!" }; const handleLoginForm = (event) => { event.preventDefault(); if (id === userInfo.id && password === userInfo.password) { dispatch({ type: "LOGIN_SUCCESS", payload: userInfo }); } else if (id !== userInfo.id && password === userInfo.password) { dispatch({ type: "MISS_ID" }); } else if (id === userInfo.id && password !== userInfo.password) { dispatch({ type: "MISS_PASSWORD" }); } else { dispatch({ type: "LOGIN_FAILURE" }); } }; return ( <form action="" onSubmit={handleLoginForm}> <label>ID</label> <input type="text" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setId(event.target.value)} /> <br /> <br /> <label>Password</label> <input type="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setPassword(event.target.value)} /> <br /> <br /> <button>๋กœ๊ทธ์ธ ํ•˜๊ธฐ</button> <br /> <p>{state.message}</p> </form> ); } export default LoginForm;
LoginForm.jsx
ย 
์œ„์™€ ๊ฐ™์ด ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ•˜์—ฌ๋„ LoginForm ์ปดํฌ๋„ŒํŠธ์—์„œ App ์ปดํฌ๋„ŒํŠธ์˜ state์™€ dispatch๋ฅผ props๋กœ ์ „๋‹ฌ ๋ฐ›์•˜๊ธฐ ๋•Œ๋ฌธ์— App.jsx ํŒŒ์ผ ํ•˜๋‚˜์—์„œ ์ž‘์„ฑํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•˜๊ณ  ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ํด๋” ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ  ๋ถ€๋ชจ์™€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ ๊ฐ„์˜ ๊ด€๊ณ„๊ฐ€ ๊นŠ์–ด์งˆ์ˆ˜๋ก ๋˜ ๋‹ค์‹œ props drilling์˜ ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐํ•ด์•ผํ•  ๊ณผ์ œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— useReducer๋Š” useContext์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์ข‹์€ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ ์ด์–ด์ง€๋Š” 5.4 useReducer์™€ useContext ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ์—์„œ ๋ณด๋‹ค ์ž์„ธํžˆ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

5.4 useReducer์™€ useContext ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ

5.4.1 useReducer์˜ ๋ฌธ์ œ์ 

useReducer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ƒํƒœ ๊ด€๋ฆฌ์— ๋Œ€ํ•œ ๋กœ์ง๋“ค์ด ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ถ„๋ฆฌ๋˜์–ด ์‰ฝ๊ฒŒ ์žฌ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ dispatch์™€ reducer๋ฅผ ํ†ตํ•ด ๋ณ€๊ฒฝํ•  ๋ฐ์ดํ„ฐ๋Š” ์ตœ์ข…์ ์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ปดํฌ๋„ŒํŠธ๊นŒ์ง€ ์ „๋‹ฌํ•ด ์ค˜์•ผ ํ•˜๋ฏ€๋กœ props drilling์ด ์ƒ๊ธด๋‹ค๋Š” ๋‹จ์ ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
์ž‘์€ ๊ทœ๋ชจ์˜ ํ”„๋กœ์ ํŠธ์ผ ๊ฒฝ์šฐ props๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊นŠ์ด๊ฐ€ ๊นŠ์ง€ ์•Š์ง€๋งŒ, ๊ทœ๋ชจ๊ฐ€ ์žˆ๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๊ฒฝ์šฐ์—๋Š” props๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์„ ๋Š๋‚„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 5-11๊ทธ๋ฆผ 5-11
๊ทธ๋ฆผ 5-11
ย 
์•ž์—์„œ ์‚ดํŽด๋ณธ ์˜ˆ์ œ์—๋Š” LoginForm ์ปดํฌ๋„ŒํŠธ์—์„œ state์™€ dispatch๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด App ์ปดํฌ๋„ŒํŠธ์—์„œ๋ถ€ํ„ฐ props๋กœ ๊ฐ’์„ ๋‚ด๋ ค์ค˜์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด, ์ƒํƒœ ๊ด€๋ฆฌ๋„ ํ•˜๋ฉด์„œ props drilling์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”? ๋ฐ”๋กœ useContext๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์•ž์—์„œ ์‚ดํŽด๋ณธ ๋กœ๊ทธ์ธ ์˜ˆ์ œ๋ฅผ ์ด์šฉํ•˜์—ฌ props drilling์„ ํ•ด๊ฒฐํ•ด ๋ด…์‹œ๋‹ค.
ย 
๐Ÿ’ก
props drilling ์ค‘์ฒฉ๋œ ์—ฌ๋Ÿฌ ๊ณ„์ธต์˜ ์ปดํฌ๋„ŒํŠธ์— props๋ฅผ ์ „๋‹ฌํ•ด ์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹จ๊ณ„์ ์œผ๋กœ ์ผ์ผ์ด props๋ฅผ ๋„˜๊ฒจ์คŒ์œผ๋กœ์จ ํ•ด๋‹น props๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์—๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์ œ๊ณต๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆผ 5-12๊ทธ๋ฆผ 5-12
๊ทธ๋ฆผ 5-12
ย 
ย 

5.4.2 props drilling ํ•ด๊ฒฐํ•˜๊ธฐ

ย 
Context.jsx
import { createContext, useReducer } from "react"; import Reducer from "./Reducer"; const INITIAL_STATE = { isLogin: false, message: "" }; export const Context = createContext(INITIAL_STATE); export const ContextProvider = ({ children }) => { const [state, dispatch] = useReducer(Reducer, INITIAL_STATE); return ( <Context.Provider value={{ state, dispatch, }} > {children} </Context.Provider> ); }; export default Context;
Context.jsx
ย 
๋จผ์ €, ์ดˆ๊นƒ๊ฐ’์œผ๋กœ ๋“ค์–ด๊ฐˆ ์ƒํƒœ๋ฅผ INITIAL_STATE๋ผ๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ ์–ธํ•ด ์ค€ ํ›„, createContext ์•ˆ์— ๋„ฃ์–ด Context ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋‹ค์Œ ContextProvider ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  useReducer ์•ˆ์— ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” reducer ํ•จ์ˆ˜, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” INITIAL_STATE๋ฅผ ๋„ฃ์–ด ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ContextProvider ์ปดํฌ๋„ŒํŠธ๋Š” Context.Provider ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, value ์†์„ฑ์„ ํ†ตํ•ด ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•  ๊ฐ’์„ ์ง€์ •ํ•ด ์ค๋‹ˆ๋‹ค.
ย 
index.jsx
import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; import { ContextProvider } from "./context/Context"; const container = document.getElementById("root"); const root = createRoot(container); root.render( <ContextProvider> <App /> </ContextProvider> );
index.jsx
ย 
Context.jsx์—์„œ ์ž‘์„ฑํ•œ ContextProvider ์ปดํฌ๋„ŒํŠธ๋ฅผ import ์‹œ์ผœ์ค๋‹ˆ๋‹ค. ๊ทธ๋‹ค์Œ, ContextProvider ์ปดํฌ๋„ŒํŠธ๋กœ App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ์ค๋‹ˆ๋‹ค. ์ด์ œ ContextProvider ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž‘์„ฑํ•œ value ๊ฐ’์„ App์—์„œ๋„ ์ „๋‹ฌ๋ฐ›์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
ย 
App.jsx
import { useContext } from "react"; import LoginForm from "./components/LoginForm"; import Context from "./context/Context"; import "./app.css"; function App() { const { state, dispatch } = useContext(Context); return ( <div className="main"> {state.isLogin ? ( <> <strong>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค~ ๋ผ์ด์บฃ๋‹˜!</strong> <img src="https://paullab.co.kr/images/message_licat.png" alt="๋ผ์ด์บฃ" /> <button onClick={() => dispatch({ type: "LOGOUT" })}>๋กœ๊ทธ์•„์›ƒ</button> </> ) : ( <LoginForm /> )} </div> ); } export default App;
App.jsx
ย 
useContext(Context)๋ฅผ ํ†ตํ•ด Context๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ปจํ…์ŠคํŠธ ๋‚ด์˜ ๋ณ€์ˆ˜ state์™€ dispatch๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ, ์ปจํ…์ŠคํŠธ๋ฅผ ํ†ตํ•ด ๋ฐ›์•„์˜จ state.isLogin์ด true ์ด๋ฉด ๋กœ๊ทธ์ธ์ด ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ ์‚ฌ์šฉ์ž๋ฅผ ํ™˜์˜ํ•˜๋Š” ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๊ณ  false ์ด๋ฉด LoginForm ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ, ๊ธฐ์กด์—๋Š” state์™€ dispatch๋ฅผ LoginForm ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ props๋กœ ๊ฐ’์„ ์ „๋‹ฌํ•ด ์ค˜์•ผ ํ–ˆ์œผ๋‚˜ ์ด์ œ๋Š” ๊ทธ๋Ÿด ํ•„์š”๊ฐ€ ์—†๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์™œ ๊ทธ๋Ÿฐ ๊ฑด์ง€ LoginForm ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ดํŽด๋ด…์‹œ๋‹ค.
ย 
ย 
LoginForm.jsx
import { useState, useContext } from "react"; import Context from "../context/Context"; function LoginForm() { const [id, setId] = useState(''); const [password, setPassword] = useState(''); const { state, dispatch } = useContext(Context); const userInfo = { id: "licat", password: "weniv!!" }; const handleLoginForm = (event) => { event.preventDefault(); if (id === userInfo.id && password === userInfo.password) { dispatch({ type: "LOGIN_SUCCESS", payload: userInfo }); } else if (id !== userInfo.id && password === userInfo.password) { dispatch({ type: "MISS_ID" }); } else if (id === userInfo.id && password !== userInfo.password) { dispatch({ type: "MISS_PASSWORD" }); } else { dispatch({ type: "LOGIN_FAILURE" }); } }; return ( <form action="" onSubmit={handleLoginForm}> <label>ID</label> <input type="text" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setId(event.target.value)}/> <br /> <br /> <label>Password</label> <input type="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" onChange={(event) => setPassword(event.target.value)}/> <br /> <br /> <button>๋กœ๊ทธ์ธ ํ•˜๊ธฐ</button> <br /> <p>{state.message}</p> </form> ); } export default LoginForm;
LoginForm.jsx
ย 
๊ทธ๋ฆผ 5-13๊ทธ๋ฆผ 5-13
๊ทธ๋ฆผ 5-13
ย 
LoginForm ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ useContext(Context)๋ฅผ ํ†ตํ•ด ์ปจํ…์ŠคํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ปจํ…์ŠคํŠธ ๋‚ด์˜ ๋ณ€์ˆ˜ state์™€ dispatch๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ ์–ธํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. LoginForm ์ปดํฌ๋„ŒํŠธ์—์„œ state์™€ dispatch๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด props drilling์ด ์ผ์–ด๋‚˜๋Š” ์ผ์„ ์—†์• ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 5-14๊ทธ๋ฆผ 5-14
๊ทธ๋ฆผ 5-14
ย 
form์˜ input์— ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด useState์˜ setId์™€ setPassword ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด id์™€ password ๊ฐ’์ด ์—…๋ฐ์ดํŠธ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ฐ’์„ ์ž…๋ ฅ ํ›„ form์„ ์ œ์ถœํ•˜๋ฉด ๋‚ด๊ฐ€ ์ž…๋ ฅํ•œ ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ userInfo์™€ ์ผ์น˜ํ•œ ์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๊ฐ๊ฐ ๋‹ค๋ฅธ action์˜ type์ด reducer๋กœ ์ „๋‹ฌ๋˜๊ณ  ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋ฉ๋‹ˆ๋‹ค.
ย