HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🗂️
React, Hook 들어오네!?
/
😊
회의록
/
🧶
useReducer
🧶

useReducer

 
🍀
useReducer - 백업
 
텍스트에서는 reducer !!!
[다은님]
  • 초반 도입부는 은행으로 동작 원리를 설명 (reducer, action, type, dispatch, etc …)
📎
다은님 - useReducer
[지형님]
  • 예제코드에 대한 설명
✏️
지형님 - useReducer
[수민님]
  • 예제코드 + alpha (ex. 왜 reducer는 순수함수로 작성해야하는가? 등)
📌
수민님 - useReducer
[정음님]
  • 아래 글을 참고해서 작성해주시면 될 것 같아요~ (저희 예제코드와의 비교가 필요하다면 적용해주시면 좋을 것 같아요)
🗒️
정음님 - useReducer
리덕스 잘 쓰고 계시나요? - 리디주식회사 RIDI Corporation
리액트 생태계에는 다양한 상태 관리 관련 라이브러리들이 있습니다. 그 중에서, 리덕스 (Redux)가 가장 많이 사용되고 있지요. 통계에 따르면 약 48%의 개발자들이 리액트 프로젝트에서 현재 리덕스를 사용하고 있습니다 [1]. 그런만큼, 현업에서 리덕스를 사용중인 분들이 많을 것이라고 예상합니다. 리덕스의 사용방식에 있어서는 딱 정해진 규칙이 존재하지 않기 때문에 개발자들 모두 자신만의 방식으로 서로 다르게 사용을 하고 있습니다.
리덕스 잘 쓰고 계시나요? - 리디주식회사 RIDI Corporation
https://ridicorp.com/story/how-to-use-redux-in-ridi/
리덕스 잘 쓰고 계시나요? - 리디주식회사 RIDI Corporation
React State Management without Redux
Mimicking Redux features with React Hooks and Context API In the React world, one of the first ideas that come to mind when thinking about state management is using the Redux library. Redux provides a predictable state container to centralize the global store of your application.
React State Management without Redux
https://medium.com/strands-tech-corner/react-state-management-without-redux-d39c7087039d
React State Management without Redux
 
 
 
  1. 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.js
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.js
 
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.js
.main { margin-top: 30px; text-align: center; } img { display: block; width: 400px; margin: 0 auto; height: 400px; }
app.css
 
  1. 로그인 과정을 조금 더 세분화한 코드
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.js
 
  1. useReducer 적용 - App.js 하나에 모두 작성
- console.log로 찍히는 action.type과 state가 이전 상태의 것들임에 유의!
import { useState, useReducer } from "react"; import "./app.css"; const reducer = (state, action) => { console.log("old State: ", action.type, state); switch (action.type) { case "LOGIN_SUCCESS": console.log(action.type, state); return { user: action.payload, isLogin: true, message: "로그인 성공!", }; case "MISS_ID": console.log(action.type, state); return { isLogin: false, message: "아이디를 다시 한번 기억해보세요~", }; case "MISS_PASSWORD": console.log(action.type, state); return { isLogin: false, message: "비밀번호를 다시 한번 기억해보세요~", }; case "LOGIN_FAILURE": console.log(action.type, state); return { isLogin: false, message: "아이디랑 비밀번호가 모두 틀렸어요~ ㅠㅠ", }; case "LOGOUT": console.log(action.type, state); return { 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.js
 
notion image
 
notion image
 
notion image
 
notion image
 
notion image
 
 
  1. 파일 분리에 따른 props drilling 발생
 
const Reducer = (state, action) => { console.log("old State: ", action.type, state); switch (action.type) { case "LOGIN_SUCCESS": console.log(action.type, state); return { user: action.payload, isLogin: true, message: "로그인 성공!", }; case "MISS_ID": console.log(action.type, state); return { isLogin: false, message: "아이디를 다시 한번 기억해보세요~", }; case "MISS_PASSWORD": console.log(action.type, state); return { isLogin: false, message: "비밀번호를 다시 한번 기억해보세요~", }; case "LOGIN_FAILURE": console.log(action.type, state); return { isLogin: false, message: "아이디랑 비밀번호가 모두 틀렸어요~ ㅠㅠ", }; case "LOGOUT": console.log(action.type, state); return { isLogin: false, message: "로그아웃!", }; default: return state; } }; export default Reducer;
Reducer.js
 
import { useReducer } from "react"; import Reducer from "./context/Reducer"; import LoginForm from "./components/LoginForm"; import "./app.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.js
 
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.js
  • 파일을 분리하였음에도 정상 작동함
====== (위에까지 지형님이 설명해야함)
  1. useContext 사용
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.js
 
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.js
 
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.js
 
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.js
 
  • props로 LoginForm.js에게 state, dispatch를 전달하지 않아도 접근이 가능하다.
 
 

최종 코드

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.js
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.js
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.js
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.js
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.js