5.1 useReducer๋?5.1.1. useReducer ๊ธฐ๋ณธ ๊ตฌ์กฐ5.2 reducer ํจ์๋?5.2.1 action๊ณผ type5.2.2 reducer ํจ์์ ์กฐ๊ฑด - ์์ ํจ์5.2.3 reducer ํจ์์ ์กฐ๊ฑด - ์ํ ๋ณ์ด(state mutation) ์ง์5.3 useReducer ์ฌ์ฉํด๋ณด๊ธฐ5.3.1 ์์ ์ค๋ช
5.3.2 src ํด๋ ๊ตฌ์กฐ5.3.3 useState๋ก ๊ตฌํ๋ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ5.3.4 useReducer๋ก ๊ตฌํํ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ5.3.5 ์ปดํฌ๋ํธ์ ๋ถ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ props drilling5.4 useReducer์ useContext ํจ๊ป ์ฌ์ฉํ๊ธฐ5.4.1 useReducer์ ๋ฌธ์ ์ 5.4.2 props drilling ํด๊ฒฐํ๊ธฐ
ย
5.1 useReducer๋?
์ปดํฌ๋ํธ์ state๋ฅผ ์์ฑํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ํด React์์๋ ๊ธฐ๋ณธ Hook์ธ useState์ useState๋ก๋ถํฐ ํ์๋ ์ถ๊ฐ์ ์ธ Hook์ธ useReducer๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ๋ Hook์ ๊ฐ์ ์ญํ ์ ํ์ง๋ง useState๋ ์ฌ์ฉ์๊ฐ ์ง์ ์ํ์ ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค๋ฉด useReducer๋ action ๊ฐ์ฒด์ reducer ํจ์๋ฅผ ํตํด ์ํ์ ์ ๊ทผํ๋ค๋ ์ฐจ์ด์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
ย
useReducer๋ฅผ ์ฝ๊ฒ ์ดํดํ๊ธฐ ์ํด์ ๊ทธ๋ฆผ์ ํตํด useState์ useReducer์ ์ฐจ์ด๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
ย
ย
์ ๊ทธ๋ฆผ์ useState๋ฅผ ์ํ์ ๋น๋์ด ์ค๋ช
ํ ๊ฒ์
๋๋ค. 100์์ ์
๊ธํ ๊ฒ์ด๋ผ๋ ์๊ตฌ์ฌํญ์ ๊ฐ์ง ์ฌ์ฉ์์ ์ฌ์ฉ์์ ๊ณ์ข์ธ state๊ฐ ์กด์ฌํฉ๋๋ค. ์ฌ์ฉ์๋ ์
๊ธ์ ํ๊ธฐ ์ํด์ ์ง์ ๊ณ์ข์ ์
๊ธ ๋ด์ญ์ ์์ฑํด์ผ ํฉ๋๋ค. ์ด๋ ์ง์ state๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒ์ด setState๊ฐ ๋ฉ๋๋ค.
ย
ย
์ ๊ทธ๋ฆผ์ useReducer์ ํ๋ฆ์ ์ํ์ผ๋ก ํํํ ๊ฒ์
๋๋ค. ์ฌ์ฉ์๋ โ์
๊ธ์ ํ ๊ฒ์ด๋คโ๋ผ๋ ์๊ตฌ์ฌํญ์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, useState์ ๋ฌ๋ฆฌ ์ฐฝ๊ตฌ ์ง์๊ณผ ์ํ ์์คํ
์ด๋ผ๋ ์ค๊ฐ์ ์ญํ ์ด ์ถ๊ฐ๋์์ต๋๋ค. ์ด๋, ์ฌ์ฉ์๋ โ์
๊ธโ์ด๋ผ๋ action์ ์ฐฝ๊ตฌ ์ง์์๊ฒ ๋ณด๋ด๋ฉด ์ด๋ฅผ ์ํ ์์คํ
์ ์ ๋ฌํ๊ณ ์ฌ์ฉ์ ๊ณ์ข์ state๋ฅผ ์
๋ฐ์ดํธํ๊ฒ ๋ฉ๋๋ค.
ย
์ด๋ ๊ฒ ์ฌ์ฉ์์ ์๊ตฌ ์ฌํญ์ด action์ด๋ฉฐ dispatch์ ์ญํ ์ ํ๋ ์ฐฝ๊ตฌ ์ง์์ด reducer์ ์ญํ ์ ํ๋ ์ํ ์์คํ
์ ์ ๋ฌํ๋ฉด ๊ณ์ข์ state๋ฅผ ์
๋ฐ์ดํธํ ์ ์๋ ๊ฒ์
๋๋ค. useReducer๋ฅผ ํ์ฉํ๋ฉด ์
๊ธ์ด๋ ์ถ๊ธ ๋ฑ ๋ค์ํ ์ผ์ ์ฌ์ฉ์๊ฐ ์ง์ ์ฒ๋ฆฌํ์ง ์๊ณ action์ผ๋ก ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ณต์กํ state๋ฅผ ๋ค๋ฃจ์ด์ผ ํ๋ค๋ฉด useReducer๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
ย
ย
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;
ย
ย
์ฐ๋ฆฌ์๊ฒ ์ต์ํ 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;
ย
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 />);
ย
์๋์ 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;
ย
์๋์ 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;
ย
.main { margin-top: 30px; text-align: center; } img { display: block; width: 400px; margin: 0 auto; height: 400px; }
ย
์์์ useState๋ฅผ ์ด์ฉํ ๊ฐ๋จํ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๊ตฌํ ๋ด์ฉ์ ์ดํด๋ณด์์ต๋๋ค. ํ์ง๋ง, ์กฐ๊ธ ๋ ์ธ๋ถ์ ์ผ๋ก ์ดํด๋ณด๋ฉด ๋ก๊ทธ์ธ ๊ณผ์ ์ ์ด 4๊ฐ์ง ๊ฒฝ์ฐ์ ์๋ก ๋๋์ด ์๊ฐํ ์ ์์ต๋๋ค.
ย
- id์ password๊ฐ ๋ชจ๋ ์ผ์นํ๋ ๊ฒฝ์ฐ โ ๋ก๊ทธ์ธ ์ฑ๊ณต
- id๋ง ์ผ์นํ๋ ๊ฒฝ์ฐ โ ๋ก๊ทธ์ธ ์คํจ
- password๋ง ์ผ์นํ๋ ๊ฒฝ์ฐ โ ๋ก๊ทธ์ธ ์คํจ
- 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;
ย
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;
ย
์ด์ ์์ ์ฝ๋๊ฐ ์ ๋์ํ๋์ง ์ดํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ๋ค์์ ๊ฐ๊ฐ์ ๊ฒฝ์ฐ์ ๋ํ ๋ธ๋ผ์ฐ์ ํ๋ฉด์ ์บก์ณํ ๋ชจ์ต์
๋๋ค.(์ฐธ๊ณ : ์
๋ ฅ๋ด์ฉ์ ๋ณด์ด๊ธฐ ์ํด password ์
๋ ฅ์ฐฝ์ธ input ์์์ type์
text
๋ก ๋ณํํ์ฌ ์งํํ์์ต๋๋ค.)ย
- ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ ๊ฒฝ์ฐ (โLOGIN_SUCCESSโ)
- id๊ฐ ํ๋ฆฐ ๊ฒฝ์ฐ ("MISS_ID")
ย
- password๊ฐ ํ๋ฆฐ ๊ฒฝ์ฐ ("MISS_PASSWORD")
- id์ password๊ฐ ๋ชจ๋ ํ๋ฆฐ ๊ฒฝ์ฐ ("LOGIN_FAILURE")
ย
- ๋ก๊ทธ์์ ๋ฒํผ์ ๋๋ฅธ ๊ฒฝ์ฐ ("LOGOUT")
ย
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;
ย
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;
ย
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 ์ปดํฌ๋ํธ์์ 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๋ฅผ ๋๊ฒจ์ฃผ๋ ๋ฒ๊ฑฐ๋ก์์ ๋๋ ์ ์์ ๊ฒ์
๋๋ค.
ย
ย
์์์ ์ดํด๋ณธ ์์ ์๋ LoginForm ์ปดํฌ๋ํธ์์ state์ dispatch๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด App ์ปดํฌ๋ํธ์์๋ถํฐ props๋ก ๊ฐ์ ๋ด๋ ค์ค์ผ ํ์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด, ์ํ ๊ด๋ฆฌ๋ ํ๋ฉด์ props drilling์ ํผํ๊ธฐ ์ํด์๋ ์ด๋ป๊ฒ ํด์ผ ํ ๊น์? ๋ฐ๋ก
useContext
๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. ์์์ ์ดํด๋ณธ ๋ก๊ทธ์ธ ์์ ๋ฅผ ์ด์ฉํ์ฌ props drilling์ ํด๊ฒฐํด ๋ด
์๋ค.ย
props drilling
์ค์ฒฉ๋ ์ฌ๋ฌ ๊ณ์ธต์ ์ปดํฌ๋ํธ์ props๋ฅผ ์ ๋ฌํด ์ฃผ๋ ๊ฒ์
๋๋ค. ๋จ๊ณ์ ์ผ๋ก ์ผ์ผ์ด props๋ฅผ ๋๊ฒจ์ค์ผ๋ก์จ ํด๋น props๋ฅผ ์ฌ์ฉํ์ง ์๋ ์ปดํฌ๋ํธ๋ค์๋ ๋ฐ์ดํฐ๊ฐ ์ ๊ณต๋๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
ย
ย
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;
ย
๋จผ์ , ์ด๊น๊ฐ์ผ๋ก ๋ค์ด๊ฐ ์ํ๋ฅผ
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> );
ย
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;
ย
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 ์ปดํฌ๋ํธ์์๋
useContext(Context)
๋ฅผ ํตํด ์ปจํ
์คํธ๋ฅผ ํธ์ถํ๊ณ ์ปจํ
์คํธ ๋ด์ ๋ณ์ state์ dispatch๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ์ ์ธํ๋ฉด ๋๊ธฐ ๋๋ฌธ์
๋๋ค. LoginForm ์ปดํฌ๋ํธ์์ state์ dispatch๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด props drilling์ด ์ผ์ด๋๋ ์ผ์ ์์ ์ค ์ ์์ต๋๋ค.ย
ย
form์ input์ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ๋ฉด useState์ setId์ setPassword ํจ์๋ฅผ ํตํด id์ password ๊ฐ์ด ์
๋ฐ์ดํธ ๋ฉ๋๋ค. ๋ํ, ๊ฐ์ ์
๋ ฅ ํ form์ ์ ์ถํ๋ฉด ๋ด๊ฐ ์
๋ ฅํ ์์ด๋/๋น๋ฐ๋ฒํธ๊ฐ userInfo์ ์ผ์นํ ์ง ์ฌ๋ถ์ ๋ฐ๋ผ ๊ฐ๊ฐ ๋ค๋ฅธ action์ type์ด reducer๋ก ์ ๋ฌ๋๊ณ ์ํ๊ฐ ์
๋ฐ์ดํธ ๋ฉ๋๋ค.
ย