텍스트에서는 reducer !!!
[다은님]
- 초반 도입부는 은행으로 동작 원리를 설명 (reducer, action, type, dispatch, etc …)
[지형님]
- 예제코드에 대한 설명
[수민님]
- 예제코드 + alpha (ex. 왜 reducer는 순수함수로 작성해야하는가? 등)
[정음님]
- 아래 글을 참고해서 작성해주시면 될 것 같아요~ (저희 예제코드와의 비교가 필요하다면 적용해주시면 좋을 것 같아요)
- 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 />);
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;
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; }
- 로그인 과정을 조금 더 세분화한 코드
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;
- 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;





- 파일 분리에 따른 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;
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;
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;
- 파일을 분리하였음에도 정상 작동함
====== (위에까지 지형님이 설명해야함)
- 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> );
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;
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;
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;
- 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> );
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;
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;
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 { 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;