useReducer를 이용한 상태 관리



1. useReducer란?

useReduceruseState보다 더 복잡한 상태 관리 로직을 처리하기 위한 React 훅입니다. 특히 상태가 여러 하위 요소로 구성되거나, 상태 변경 로직이 복잡한 경우에 사용됩니다. 이 훅은 컴포넌트 외부에 상태 업데이트 로직을 분리하여 코드의 가독성과 유지보수성을 높일 수 있습니다.


2. 설명

1) 초기 상태와 reducer 함수 정의

// #1. 초기 상태값 정의
const initState = { value: 0 }; // 초기 상태 값

// #2. reducer 함수 정의
// - 이 함수는 현재 상태와 액션을 받아 새로운 상태를 반환
const reducer = (prevState, action) => {
    console.log('prevState >>> ', prevState); // {value: 0}

    // action
    console.log('action >>> ', action); // {type: 'INCREMENT'}
    console.log('action.type >>> ', action.type); // INCREMENT

    switch (action.type) {
        case 'INCREMENT':
            return { value: prevState.value + 1 };
        case 'DECREMENT':
            return { value: prevState.value - 1 };
        case 'RESET':
            return initState;
        default:
            return { value: prevState.value }
    }
}
  • 초기 상태 정의
    • initState는 상태의 초기값을 정의한 객체입니다. 여기서는 value라는 상태 값을 0으로 초기화하고 있습니다.
  • reducer 함수
    • reducer 함수는 현재 상태(prevState)와 액션(action)을 받아 새로운 상태를 반환하는 순수 함수입니다. 액션은 상태를 변경하기 위한 명령을 포함하고 있으며, 이 함수는 액션의 종류에 따라 상태를 업데이트합니다.
    • switch 문을 통해 액션의 타입(INCREMENT, DECREMENT, RESET)에 따라 상태를 변경합니다.


2) UseReducerEx 컴포넌트

import React, { useReducer } from 'react';

export default function UseReducerEx() {
    // #3. useReducer 훅 사용.
    const [state, dispatch] = useReducer(reducer, initState);
    // state: 현재 상태
    // dispatch: 액션을 발생 시키는 함수 (state가 어떻게 변경되어야 하는지에 대한 힌트 제공)
    // reducer: state를 업데이트 하는 함수

    console.log('state >>> ', state); // {value: 0}

    // #4. 액션 핸들러 함수 정의
    // - 이 함수들은 'dispatch'를 호출하여 액션을 발생 시킴
    const increment = () => dispatch({ type: 'INCREMENT' });
    const decrement = () => dispatch({ type: 'DECREMENT' });
    const reset = () => dispatch({ type: 'RESET' });

    return (
        <div>
            <h1>UseReducerEx</h1>
            <h2>{state.value}</h2>
            {/* #5. 컴포넌트 랜더링 */}
            <button onClick={increment}>PLUS</button>
            <button onClick={decrement}>MINUS</button>
            <button onClick={reset}>Reset</button>
        </div>
    );
}

3) UseReducerEx 컴포넌트 분석

useReducer 훅을 사용하여 상태 관리를 수행합니다. useReducer는 두 가지 요소를 반환하는데, 첫 번째는 현재 상태(state)이고, 두 번째는 상태를 업데이트하기 위한 함수인 dispatch입니다.

  • useReducer 훅 사용
    • useReducerreducer 함수와 초기 상태값(initState)을 인자로 받아, 현재 상태와 상태를 변경할 수 있는 dispatch 함수를 반환합니다.
    • state는 현재의 상태를 나타내며, 이 상태 값은 컴포넌트가 리렌더링될 때마다 변경된 상태를 반영합니다. 예를 들어, 여기서는 value가 상태로 정의되어 있으며, 초기값은 0입니다.
    • dispatch는 상태를 변경하기 위해 호출하는 함수로, reducer 함수에 특정 액션을 전달하여 새로운 상태를 계산하도록 합니다.
  • 액션 핸들러 함수 정의
    • increment, decrement, reset 함수는 각각 dispatch를 호출하여 상태를 변경하는 액션을 발생시킵니다.
      • increment 함수는 dispatch({ type: 'INCREMENT' })를 호출하여 value를 1 증가시키는 액션을 전달합니다.
      • decrement 함수는 dispatch({ type: 'DECREMENT' })를 호출하여 value를 1 감소시키는 액션을 전달합니다.
      • reset 함수는 dispatch({ type: 'RESET' })를 호출하여 상태를 초기값으로 되돌리는 액션을 전달합니다.
    • 액션들은 dispatch에 의해 reducer 함수로 전달되며, 해당 액션의 타입에 따라 reducer 함수가 새로운 상태를 계산하고 반환합니다.
  • 컴포넌트 렌더링
    • 컴포넌트는 state.value를 화면에 출력하며, 세 개의 버튼(PLUS, MINUS, Reset)을 제공합니다. 사용자가 버튼을 클릭하면 해당 액션 핸들러가 호출되고, 이에 따라 상태가 업데이트됩니다.
    • 예를 들어, 사용자가 PLUS 버튼을 클릭하면 increment 함수가 호출되어 dispatch를 통해 INCREMENT 액션이 발생하고, 그 결과 value가 1 증가합니다. 상태가 변경되면 컴포넌트는 리렌더링되어 화면에 새로운 값이 표시됩니다.


4) 상태 변경 과정 요약

(1) 사용자가 버튼(PLUS, MINUS, Reset)을 클릭합니다.

(2) 해당 버튼의 클릭 이벤트가 발생하면, 각 액션 핸들러 함수(increment, decrement, reset)가 실행됩니다.

(3) 핸들러 함수가 dispatch를 호출하여 reducer 함수에 액션(type)을 전달합니다.

(4) reducer 함수가 현재 상태와 전달된 액션을 바탕으로 새로운 상태를 계산하여 반환합니다.

(5) 컴포넌트가 리렌더링되고, 화면에 새로운 상태값이 출력됩니다.


3. useState vs. useReducer

React에서 useStateuseReducer는 모두 컴포넌트의 상태를 관리하기 위한 훅입니다. 그러나 이 두 훅은 상태의 복잡도와 상태 변경 로직에 따라 각각 다른 상황에 적합합니다.

1) useState

  • useState는 간단한 상태를 관리하는 데 적합한 훅입니다. 예를 들어, 숫자, 문자열, 불리언 등과 같은 단일 값이나, 간단한 객체 상태를 관리할 때 유용합니다.
  • 상태가 하나 또는 두 개 정도로 단순한 경우, 그리고 상태 변경 로직이 복잡하지 않은 경우 useState를 사용하는 것이 더 직관적이고 간편합니다.

    import React, { useState } from 'react';
    
    function Counter() {
        const [count, setCount] = useState(0);
    
        const increment = () => setCount(count + 1);
        const decrement = () => setCount(count - 1);
    
        return (
            <div>
                <h2>Count: {count}</h2>
                <button onClick={increment}>Increase</button>
                <button onClick={decrement}>Decrease</button>
            </div>
        );
    }
    
    • 단순한 카운터 기능을 구현한 것입니다. 상태(count)는 단일 숫자 값이고, 상태 변경 로직도 간단하기 때문에 useState를 사용하는 것이 적절합니다.
  • 장점
    • 사용이 간편하고, 상태 변경 로직이 간단한 경우 직관적입니다.
    • 코드를 간결하게 유지할 수 있습니다.
  • 단점
    • 상태가 복잡해지거나, 여러 개의 상태가 상호 연관될 때 코드가 비대해지고 관리하기 어려워질 수 있습니다.

2) useReducer

  • useReducer는 상태가 복잡하거나, 여러 단계의 상태 변경 로직이 필요한 경우 적합한 훅입니다. 상태가 객체나 배열로 구성되어 있거나, 상태 변경 로직이 여러 가지로 분기되는 경우, useReducer를 사용하면 로직을 명확하고 구조화된 방식으로 관리할 수 있습니다.
  • useReducer는 상태 관리 로직을 컴포넌트 외부로 분리할 수 있게 해주며, reducer 함수 내에서 상태 변경 로직을 집중적으로 관리할 수 있습니다.

    import React, { useReducer } from 'react';
    
    const initialState = { count: 0 };
    
    function reducer(state, action) {
        switch (action.type) {
            case 'INCREMENT':
                return { count: state.count + 1 };
            case 'DECREMENT':
                return { count: state.count - 1 };
            case 'RESET':
                return { count: 0 };
            default:
                return state;
        }
    }
    
    function Counter() {
        const [state, dispatch] = useReducer(reducer, initialState);
    
        return (
            <div>
                <h2>Count: {state.count}</h2>
                <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increase</button>
                <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrease</button>
                <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
            </div>
        );
    }
    
    • 카운터의 상태를 관리하기 위해 useReducer를 사용하고 있습니다. reducer 함수는 다양한 액션 타입(INCREMENT, DECREMENT, RESET)에 따라 상태를 업데이트하는 로직을 관리합니다. 상태 변경 로직이 분명하게 구조화되어 있고, 상태가 복잡한 경우에도 useReducer를 사용하면 코드가 명확해집니다.
  • 장점
    • 상태가 복잡하거나, 상태 변경 로직이 여러 단계로 이루어진 경우에 더 구조적인 접근을 할 수 있습니다.
    • 상태 관리 로직을 reducer 함수로 분리하여, 컴포넌트 외부에서 관리할 수 있어 코드의 재사용성과 유지보수성이 향상됩니다.
  • 단점
    • useState에 비해 초기 설정이 복잡할 수 있으며, 상태가 단순한 경우에는 오히려 불필요하게 복잡한 코드를 작성하게 될 수 있습니다.

3) 상황에 따른 선택 기준

  • 상태의 복잡도
    • 상태가 단순하고 변경 로직이 간단한 경우 useState가 적합합니다.
    • 상태가 복잡하고, 여러 단계의 로직이 필요하거나 상태가 여러 개의 값으로 구성되어 있을 때는 useReducer가 적합합니다.
  • 코드의 가독성
    • useState는 간단한 상태 관리에 적합하며, 코드가 직관적이고 가독성이 좋습니다.
    • useReducer는 복잡한 상태 로직을 명확하게 분리하여, 가독성과 유지보수성을 높일 수 있습니다.
  • 상태 변경의 예측 가능성
    • useReducer를 사용하면 모든 상태 변경 로직이 reducer 함수에 집중되기 때문에, 상태 변화의 흐름을 더 명확하게 추적할 수 있습니다.
    • useState는 간단한 상태 변경에 적합하지만, 상태 변경 로직이 분산될 수 있습니다.