📎

다은님 - useReducer

5.1 useReducer란?

컴포넌트의 state를 생성하고 관리하기 위해 React에서는 기본 Hook인 useState와 useState로부터 파생된 추가적인 Hook인 useReducer를 제공하고 있습니다. 두 Hook은 같은 역할을 하지만 useState는 사용자가 직접 상태에 접근하는 것이 가능했다면 useReducer는 action 객체와 reducer 함수를 통해 상태에 접근한다는 차이점을 가지고 있습니다.
 
useReducer를 쉽게 이해하기 위해서 그림을 통해 useState와 useReducer의 차이를 살펴보겠습니다.
notion imagenotion image
notion imagenotion image
 
위 그림은 useReducer를 표현한 것입니다. 사용자는 위와 같은 요구사항을 가지고 있으며, useState와 달리 은행이라는 중간자 역할이 추가되었습니다. 이때, 사용자는 ‘입금’이라는 action을 은행에 보내면 은행에서 사용자 계좌의 상태를 업데이트하게 됩니다.
 
이렇게 사용자의 요구를 dispatch라고 볼 수 있으며, action에 내용을 담아 reducer의 역할을 하는 은행에 전달하면 계좌의 state를 업데이트할 수 있는 것입니다. useReducer를 활용하면 입금이나 출금 등 다양한 일을 사용자가 직접 처리하지 않고 action으로 간단하게 처리할 수 있기 때문에 복잡한 상태를 다루어야 한다면 useReducer를 사용하는 것이 좋습니다.
 
notion imagenotion image
 
useReducer의 흐름에 대해 살펴보겠습니다. state를 업데이트시키기 위해서 dispatch 함수의 인자로 action을 넣어서 reducer에 전달합니다. reducer는 우리가 넣은 action에 맞춰 state를 업데이트하게 됩니다.
 
💡
dispatch, action, reducer 이해하기 dispatch는 state 업데이트를 위한 요구이며, action은 요구의 내용, reducer는 state를 업데이트 해주는 역할이며 컴포넌트의 state를 변경하고 싶다면 reducer를 활용하여 변경하는 것입니다.
 
그렇다면 useReducer와 useState를 어떤 상황에서 어떻게 사용해야 하는지 알아보겠습니다. useReducer는 useState보다 더 복잡한 작업을 처리하는 것이 가능합니다. 상태가 단순할 경우에는 useReducer를 사용하는 것이 코드를 더 복잡하게 만들 수 있기 때문에 useState를 사용하는 것이 적합합니다. 하지만 객체나 배열같이 여러 개의 하위 값을 포함하는 복잡한 상태를 가지거나 확장성이 있을 때는 useReducer를 사용하여 상태 관리를 한다면 코드를 간결하게 해주고 유지보수를 편리하게 할 수 있게 됩니다.

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;
App.jsx
 
notion imagenotion image
 
우리에게 익숙한 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;
App.jsx
 
useState로 구현한 카운터를 useReducer를 사용하여 바꿔보았습니다. ⓵에서 useReducer를 정의합니다. ⓶에서 각 버튼의 이벤트에 대한 onClick 함수를 정의합니다. onClick 이벤트가 발생하면 dispatch는 action 값을 담아 state와 함께 ⓷으로 전달합니다. reducer에서는 전달받은 action과 일치하는 값을 찾는 조건문을 실행하여 새로운 state를 반환합니다.
 
useState를 사용한 카운터에서는 setCount로 직접 state를 변경하였지만, useReducer에서는 setState의 기능을 세분화하여 직접 상태에 접근하지 않으며 상태를 변경하는 것은 reducer 함수에서 집중적으로 처리하게 됩니다. 이러한 방식을 사용하게 되면 reducer 함수 내부에 상태를 변경하는 과정을 은닉할 수 있으며 이것이 useReducer를 사용하는 이유 중 하나입니다.
위 그림은 useState를 은행에 빗대어 설명한 것입니다. ‘입금을 할 것이다’라는 요구사항을 가진 사용자와 사용자의 계좌인 state가 존재합니다. 사용자는 입금을 하기 위해서 직접 계좌에 입금 내역을 작성해야 합니다. 이때 직접 state를 변경하는 것이 setState가 됩니다.