-
useReducer로 상태관리 연습해보기REACT 2025. 12. 2. 15:24
useState는 어떻더라
기억이 가물가물하기 때문에 다시 복습을 해보기위해 아래코드를 참고해보자
function App() { const [todos, setTodos] = useState(mockDate); const idRef = useRef(3); const onCreate = (content) => { const newTodo = { id: idRef.current++, isDone: false, content: content, date: new Date().getTime(), }; setTodos([newTodo, ...todos]); }; const onUpdate = (targetId) => { setTodos( todos.map((todo) => todo.id === targetId ? { ...todo, isDone: !todo.isDone } : todo ) ); }; const onDelete = (targetId) => { setTodos(todos.filter((todo) => todo.id !== targetId)); }; return ( <div className="App"> <Header /> <Editor onCreate={onCreate} /> <List todos={todos} onUpdate={onUpdate} onDelete={onDelete} /> </div> ); }onCreate, onUpdate, onDelete 같은 함수에서 todos라는 상태값을 관리하게위해 setTodos를 쓰고있기 때문에
반드시 App 컴포넌트 안에서만 작성할 수 있었다
그러나 함수가 많아지거나 상태관리가 더 복잡해진다면???
UI를 구성하라고 만든 컴포넌트의 역할보다 컴포넌트 상태관리 코드가 길어져 가독성이 떨어지고 유지보수가 어려워지게 된다.
그래서 useReducer가 필요한것이다.
useReducer가 뭔데
출처 - 이정환님의 한입크기로 잘리먹는 리액트 (강추👍) useState랑 동일하게 상태관리를 하는 React 훅(Hook)이다.
그런데 상태관리 코드를 컴포넌트 내부에 적지않고 외부로 분리가능한 장점이 있다.
useReducer는 기본적으로 이렇게 생겨따
const [state, dispatch] = useReducer(reducer함수, 초기 state, 선택사항 함수);1번째 인자로 다음 상태를 변화시켜야할 reducer 함수
2번째 인자는 초기 state가 가지는 값
3번째 인자는 선택사항으로 2번째 인자인 state를 지연생성하기 위한 함수다. 초기 state를 여기에 전달해 계산해준다.
그리고 보이는바와 같이 useState와 비슷하게 앞에 2개의 엘리먼트로 구성된 배열을 반환한다
1. state : 말그대로 지금 state값, 위에 설명한 2번째 인자값에서 불러와 변경될수도 있음
2. dispatch 함수 : state를 새로운값으로 업데이트, 리렌더링 할 함수로 action 인자를 갖는다.
dispatch -> reducer
action을 갖는다고 했는데 무슨말인지 하나도 모르겠다면 정상이고 대충 이렇게 생겼고
그안에 어떤 상황 일때 추가정보를 객체처럼 구성할수있다.
dispatch({ type: "DECREASE", data: 1, });dispatch 쓰면 나중에 쓸 reducer함수의 action 객체로 들어가게 된다
function reducer(state, action) { switch (action.type) { case "INCREASE": return state + action.data; case "DECREASE": return state - action.data; default: return state; } }이렇게 reducer 함수 내에서 switch문을 활용해 dispatch에서 적용했던 정보들로 state값을 컨트롤할수있다.
물론 if문 써도 된다.
근데 switch문을 더 많이쓴다고 하니 그냥 시키는대로 쓰도록하자전체적으로 코드를 살펴보면 이렇다.
import { useReducer } from "react"; //reducer:변환기 //-> 상태를 실제로 변화시키는 변환기 역할 function reducer(state, action) { switch (action.type) { case "INCREASE": return state + action.data; case "DECREASE": return state - action.data; default: return state; } } const Exam = () => { // dispatch:발송하다, 급송하다 // -> 상태변화가 있어야 한다는 사실을 알리는, 발송하는 함수 const [state, dispatch] = useReducer(reducer, 0); const onClickPlus = () => { // 인수: 상태가 어떻게 변화되길 원하는지 // -> 액션객체 dispatch({ type: "INCREASE", data: 1, }); }; const onClickMinus = () => { dispatch({ type: "DECREASE", data: 1, }); }; return ( <div> <h1>{state}</h1> <button onClick={onClickPlus}>+</button> <button onClick={onClickMinus}>-</button> </div> ); }; export default Exam;정리해보면
useReducer로 state와 dispatch값을 정의한 다음에
dispatch에는 각 상황에 맞는 프로퍼티를 넣어주고
reducer함수를 정의할때 action 자리에 dispatch를 넣어 조건문으로 쓴다고 생각하면 되겟다.
오늘의 실습문제
1. 다음 Counter 컴포넌트에서 count를 1씩 증가/감소시키는 버튼을 만들어보세요.
반드시 useReducer 를 사용해 주셔야 합니다.import { useReducer } from "react"; export default function Counter() { function reducer(state, action) { switch (action.type) { case "INCREMENT": return { count: state.count + action.data }; case "DECREMENT": return { count: state.count - action.data }; default: return state; } } // TODO: reducer 함수와 초기 상태를 정의하고 useReducer로 연결하세요. const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <h2>문제 1️⃣ 카운터</h2> <p>현재 값: {state.count}</p> <button onClick={() => dispatch({ type: "INCREMENT", data: 1 })}> +1 </button> <button onClick={() => dispatch({ type: "DECREMENT", data: 1 })}> -1 </button> </div> ); }예제랑 너무 비슷한 코드여서 사실 프로퍼티값만 좀 바꾸면 끝이였음;;
2. name, email 두 개의 input을 관리하는 폼을 만드세요. 입력 시 상태가 reducer를 통해 업데이트되어야 합니다.
import { useReducer } from "react"; export default function SignupForm() { // TODO: reducer, initialState 작성 function reducer(state, action) { switch (action.type) { case "CHANGE_INPUT": if (action.field === "name") { return { ...state, name: action.value }; } if (action.field === "email") { return { ...state, email: action.value }; } return state; default: return state; } } const [state, dispatch] = useReducer(reducer, { name: "", email: "" }); return ( <form> <h2>문제 2️⃣ 회원가입 폼</h2> <input name="name" placeholder="이름" value={state.name} onChange={(e) => dispatch({ type: "CHANGE_INPUT", field: "name", value: e.target.value, }) } /> <input name="email" placeholder="이메일" value={state.email} onChange={(e) => dispatch({ type: "CHANGE_INPUT", field: "email", value: e.target.value, }) } /> <p> 입력된 이름: {state.name} / 이메일: {state.email} </p> </form> ); }초기값으로 name, email을 빈문자열로 초기화해주고 reducer 함수내에서 중첩 조건문을 썼다.
먼저 dispatch의 type 값이 같았기때문에 한번 걸러주고 그다음 field 값으로 검사해 값을 리턴시켰다.
근데 여기서 반복되는 조건문을 더 간결하게 코드를 짜고싶다면
// 반복되는 if문을 더 간결하게 쓰다고싶다면? function reducer(state, action) { switch (action.type) { case "CHANGE_INPUT": if (action.field === "name") { return { ...state, name: action.value }; } if (action.field === "email") { return { ...state, email: action.value }; } return state; default: return state; } } // 이렇게 계산된 속성으로 쓰는것도 가능하다고 한다 (feat.클로드) function reducer(state, action) { switch (action.type) { case "CHANGE_INPUT": return { ...state, [action.field]: action.value }; default: return state; } }...이해는 하겠는데 막상쓰라면 못쓸거같으니까 그냥 그렇구나 넘어가도록하자3. 할 일을 추가/삭제/완료 표시할 수 있는 Todo 앱을 만드세요. useReducer로 상태를 제어해야 합니다.
import { useReducer, useState } from "react"; export default function TodoList() { // TODO: reducer, initialState 작성 function reducer(state, action) { switch (action.type) { case "ADD": return [...state, { id: Date.now(), text: action.text, done: false }]; // 새로운 todo를 배열끝에 추가 case "TOGGLE": return state.map((todo) => todo.id === action.id ? { ...todo, done: !todo.done } : todo ); // id done값을 반대로 case "REMOVE": return state.filter((todo) => todo.id !== action.id); // id값이 일치하지 않는 것들만 남겨서 삭제 default: return state; } } const [state, dispatch] = useReducer(reducer, []); const [text, setText] = useState(""); return ( <div> <h2>문제 3️⃣ Todo List</h2> <input value={text} onChange={(e) => setText(e.target.value)} placeholder="할 일 입력" /> <button onClick={() => { dispatch({ type: "ADD", text }); setText(""); }} > 추가 </button> <ul> {state.map((todo) => ( <li key={todo.id}> <label> <input type="checkbox" checked={todo.done} onChange={() => dispatch({ type: "TOGGLE", id: todo.id })} /> {todo.text} </label> <button onClick={() => dispatch({ type: "REMOVE", id: todo.id })}> 삭제 </button> </li> ))} </ul> </div> ); }이번에도 type별로 조건문을 걸어주었고
ADD 케이스에선 스프레드 연산자와, Date.now() 타임스탬프로 고유한 id값을 생성해서 배열화 시켰다
TOGGLE은 map함수로 돌려 id값이 일치하는지 삼항연산자로 검사하고 true일때 논리연산자(!)로 done값을 반대로 리턴시켰다.
REMOVE는 filter함수로 id값이 일치하지않은것들만 새로운 배열을 만들어내 삭제기능을 완성했다!
겉보기엔 좀 어려워보였는데 사실 수업시간에 다 배운것들이라 기억력만 좋다면 쉽게 풀수있는 문제였다!
문제를 풀고나서 열심히 공부해야겠단 생각이 많이들었다,,
useState, useEffect 외 다른훅은 생소해서 머리가 터지려구 그런다 😵💫
'REACT' 카테고리의 다른 글
Virtual DOM부터 Memoization까지 (0) 2025.12.29 시작한건 끝내야하는 useEffect, 그리고 return (0) 2025.12.11 Props 요리조리 구워삶기 - Context API & Props Drilling (1) 2025.12.10 useState, 제대로 알고 쓰시나요? (0) 2025.12.09 굳이 createPortal로 모달을 만드는 이유는? (0) 2025.12.08