🗒️

정음님 - useReducer

4. (부록) useReducer와 useContext 함께 사용하기

useReducer를 사용하면 상태 관리에 대한 로직들이 컴포넌트에서 분리되어 쉽게 재사용 할 수 있다는 장점이 있습니다. 하지만 dispatch와 reducer를 통해 변경할 데이터를 최종적으로 변경할 컴포넌트까지 전달해 줘야 하므로 props drilling이 생긴다는 단점이 발생합니다.
작은 규모의 프로젝트의 경우 props를 전달하는 깊이가 깊지 않지만 만약 규모가 있는 프로젝트를 진행할 경우에는 props를 넘겨주는 번거로움을 느낄 수 있을 것입니다.
 
notion imagenotion image
앞에서 살펴본 예제에는 LoginForm 컴포넌트에서 state와 dispatch를 사용하기 위해 App 컴포넌트에서부터 props로 값을 내려줘야 했습니다. 그렇다면, 상태 관리도 하면서 props drilling을 피하기 위해서는 어떻게 해야 할까요? 바로 useContext를 함께 사용하면 됩니다. 앞에서 살펴본 로그인 예제를 이용하여 props drilling을 해결해 봅시다.
 
💡
props drilling 중첩된 여러 계층의 컴포넌트에 props를 전달해 주는 것입니다. 단계적으로 일일이 props를 넘겨줌으로써 해당 props를 사용하지 않는 컴포넌트들에도 데이터가 제공되는 문제가 있습니다.
참고) useContext참고) useContext
참고) useContext
 
 
 
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
먼저, 초깃값으로 들어갈 상태를 변수로 다음과 같이 { isLogin: false, message: "" } 선언해 준 후, 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
notion imagenotion image
 
LoginForm 컴포넌트에서도 useContext(Context)를 통해 컨텍스트를 호출하고 컨텍스트 내의 변수 state와 dispatch를 사용할 수 있도록 선언하면 되기 때문입니다. LoginForm 컴포넌트에서 state와 dispatch를 사용하기 위해 props drilling이 일어나는 일을 없애줄 수 있습니다.
 
notion imagenotion image
notion imagenotion image
notion imagenotion image
notion imagenotion image
 
form의 input에 아이디와 비밀번호를 입력하면 useState의 setId 함수를 통해 id 값이 업데이트됩니다. 또한, 값을 입력 후 form을 제출하면 내가 입력한 아이디/비밀번호가 userInfo와 일치한 지 여부에 따라 각각 다른 action의 type이 reducer로 전달되고 상태가 업데이트됩니다.