[React] 상태 관리를 하는 이유

jiny·2025년 1월 21일

기술 면접

목록 보기
31/78

🗣️ 상태관리는 왜 할까요? 그리고 평소 상태관리는 어떻게 하시나요?

  • 의도: 상태 관리의 목적과 평소 상태 관리 패턴 파악 및 전역 상태 관리에 대해서도 추가 답변(사용하는 라이브러리 등)을 기대하는 질문

  • 나의 답안

    상태 관리는 React의 핵심 철학인 단일 방향 데이터 흐름을 기반으로, 컴포넌트의 데이터와 동작을 일관성 있고 예측 가능하게 만드는 데 중요한 역할을 합니다.
    리액트에서 상태 관리를 해야 하는 이유는 다음과 같습니다.

    첫째, 애플리케이션의 데이터 흐름을 제어하여 데이터의 일관성을 보장하기 위해서입니다.
    리액트는 단일 방향 데이터 흐름을 따르므로, 상태가 한 방향으로만 전달됩니다.
    이를 통해 애플리케이션 데이터가 예측 가능한 방식으로 관리되고 데이터 불일치 문제를 방지할 수 있습니다.

    둘째, 사용자 인터페이스(UI)데이터 변경에 따라 동기화하기 위해서입니다.
    리액트 애플리케이션은 사용자의 상호작용, API 호출, 혹은 이벤트 처리에 따라 동적으로 UI가 업데이트되어야 합니다.
    상태가 변경되면 React가 컴포넌트를 다시 렌더링데이터와 UI가 항상 동기화됩니다.

    상태를 관리하기 위해서는 다양한 방법을 사용할 수 있습니다.
    컴포넌트 내부에서는 useState를 사용하여 상태를 관리할 수 있습니다.
    이는 단일 컴포넌트의 상태를 관리할 때 적합합니다.

    두 개 이상의 컴포넌트가 동일한 데이터를 공유해야 할 때는 상태를 상위 컴포넌트로 올려서 관리해야 합니다.
    리액트는 상태를 부모 컴포넌트에 올려서 하위 컴포넌트에 props로 전달하도록 권장합니다.

    전역 상태를 관리할 때는 Context API나 외부 라이브러리를 사용할 수 있습니다.
    대표적인 상태 관리 라이브러리로는 Redux, Zustand, Recoil 등이 있습니다.

  • 주어진 답안 (모범 답안)

    상태관리의 필요성은 리액트의 MVVM 패턴과 관련지어 설명할 수 있습니다.
    Model-View-ViewModel 패턴에 따라 상태는 Model에, 화면 UI는 View에 역할을 위임함으로써 서로의 독립성을 확보할 수 있습니다.
    이에 따라 개발자는 각각을 다른 관점에서 파악함으로써 각자의 기능에 집중해 개발할 수 있는 환경을 구축할 수 있습니다.

    저의 경우에는 이를 실현하기 위한 하나의 방법으로 커스텀 훅을 사용하여 Model 부분의 재사용성과 독립성을 향상시켰습니다.
    또한 하나의 상태를 여러 컴포넌트가 바라볼 때 나타나는 props-drilling과 같은 문제를 해결하기 위해 Redux 등의 전역 상태 관리 도구를 사용한 경험이 있습니다.


📝 개념 정리

React의 상태 관리는 컴포넌트의 상태(state)를 효율적으로 관리하고, 상태 변경에 따라 UI를 동적으로 업데이트하기 위해 사용하는 핵심 개념이다.
React는 단일 방향 데이터 흐름(unidirectional data flow)을 따르기 때문에 상태를 잘 관리하는 것이 중요한 역할을 한다.

React에서 상태 관리를 하는 이유는 애플리케이션의 데이터 흐름을 제어하고, 사용자 인터페이스(UI)를 데이터 변경에 따라 동기화하기 위함이다.
상태 관리는 React의 핵심 철학인 단일 방향 데이터 흐름을 기반으로, 컴포넌트의 데이터와 동작을 일관성 있고 예측 가능하게 만드는 데 중요한 역할을 한다.

🌟 React의 상태란?

  • 상태(state)React 컴포넌트가 기억해야 하는 데이터를 의미한다.
  • 컴포넌트 내에서 상태가 변경되면 해당 컴포넌트와 그 하위 컴포넌트가 자동으로 다시 렌더링된다.

🌟 상태 관리의 필요성

  1. 동적인 UI 업데이트
    • React 애플리케이션은 사용자의 상호작용, API 호출, 혹은 이벤트 처리에 따라 동적으로 UI가 업데이트되어야 한다.
    • 상태가 변경되면 React가 컴포넌트를 다시 렌더링해 데이터와 UI가 항상 동기화된다.
  1. 데이터의 일관성 보장
    • React는 단일 방향 데이터 흐름을 따르므로, 상태가 한 방향으로만 전달된다.
    • 이를 통해 애플리케이션 데이터가 예측 가능한 방식으로 관리되고 데이터 불일치 문제를 방지할 수 있다.
  1. 상태의 중앙 집중화
    • 컴포넌트 간 데이터가 분산되면 관리가 어렵고, 데이터의 변경이 애플리케이션 전반에 영향을 미칠 수 있다.
    • 상태 관리 도구를 사용하면 데이터의 흐름을 중앙에서 관리하여 복잡성을 줄이고, 디버깅과 유지보수를 용이하게 할 수 있다.
  1. 재사용성과 확장성
    • 여러 컴포넌트가 동일한 데이터를 공유해야 하는 경우 상태를 공유하면 불필요한 중복 코드를 제거하고, 재사용성과 확장성을 높일 수 있다.

🌟 상태 관리를 하지 않을 경우 발생할 문제

  1. 데이터 동기화 문제
    • 상태를 제대로 관리하지 않으면 데이터와 UI가 불일치하는 문제가 발생할 수 있다.
      예) 사용자가 버튼을 클릭했지만 화면에 즉각적인 변화가 없을 때
  1. 복잡한 데이터 전달
    • 상태를 상위 컴포넌트에서 관리하지 않고, 여러 하위 컴포넌트로 props를 통해 직접 전달하면 다음과 같은 문제가 발생한다.
      • Props drilling: 데이터가 불필요한 중간 컴포넌트를 통해 전달됨으로써 코드가 복잡해지고 유지보수가 어려워진다.
      • 데이터 흐름 추적이 어려워 디버깅이 힘들어진다.
  1. 유지보수 어려움
    • 상태를 체계적으로 관리하지 않으면, 애플리케이션이 커질수록 상태 변경에 따른 코드 수정 범위가 넓어지고, 버그가 발생할 확률이 높아진다.

🌟 상태 관리를 통해 얻을 수 있는 이점

  1. UI와 상태의 자동 동기화
    • React의 상태 관리 체계를 활용하면, 상태가 변경될 때 React가 알아서 UI를 업데이트한다.
    • React의 선언적 프로그래밍 덕분에 개발자가 DOM 업데이트를 직접 처리할 필요가 없다.
  1. 데이터 흐름의 명확성
    • 상태가 어디서 생성되고 변경되는지 명확히 알 수 있어, 디버깅과 데이터 흐름 추적이 쉬워진다.
      예) Redux의 단일 Store 구조를 통해 상태의 출처를 명확하게 파악할 수 있다.
  1. 생산성 향상
    • Context API, Redux, Zustand 등의 도구를 사용하면 코드의 재사용성이 증가하고, 상태 관리를 위한 반복 작업이 줄어든다.
      예) Redux Toolkit은 상태 관리에 필요한 boilerplate 코드를 대폭 줄여준다.
  1. 컴포넌트 재사용성 증가
    • 상태를 명확히 구분하고 관리하면, 상태와 로직을 분리할 수 있어 컴포넌트를 독립적으로 설계하고 재사용할 수 있다.

🌟 상태 관리는 어디에 필요할까?

  1. 컴포넌트 내부의 상태
    • 단순한 상태: 버튼 클릭 카운트, input 필드 값 등
    • 사용 예) useState
  1. 컴포넌트 간 상태 공유
    • 여러 컴포넌트가 동일한 상태를 참조해야 할 때
    • 사용 예) 부모 컴포넌트에서 상태를 관리하거나, Context API
  1. 글로벌 상태
    • 애플리케이션 전반에서 필요한 상태: 로그인 정보, 사용자 설정, 쇼핑 카트 데이터 등
    • 사용 예) Redux, Zustand, Recoil, Context API
  1. 비동기 상태
    • 서버 요청, API 데이터 페칭 등 외부 데이터 처리와 관련된 상태
    • 사용 예) Redux Toolkit, TanStack Query, React Query

🌟 상태 관리의 실제 시나리오

  1. 로그인 시스템
    로그인 상태와 사용자 정보를 전역 상태로 관리하여 모든 컴포넌트에서 참조 가능

  2. 쇼핑 카트
    사용자가 추가한 아이템 목록을 상태로 관리하여, 전역에서 접근하거나 업데이트

  3. 다크 모드 전환
    사용자 테마 설정을 Context API로 관리하여 전역에서 적용 가능

  4. 여행 계획
    일정, 장소, 예약 정보를 관리하여 여행 계획 페이지와 관련 컴포넌트 간 상태를 공유


🌟 React 상태 관리 방식

  1. 컴포넌트 내에서 관리 (Local State)

    • 컴포넌트 내부에서 useState를 사용하여 상태를 관리하는 방식이다.
    • 단일 컴포넌트의 상태를 관리할 때 적합하다.
    • React의 기본 상태 관리 방법이다.
    • 예제

      import React, { useState } from "react";
      
      function Counter() {
        const [count, setCount] = useState(0);
        
        return (
          <div>
            <p>현재 카운트: {count}</p>
            <button onClick={() => setCount(count + 1)}>증가</button>
          </div>
        );
      }
  1. 컴포넌트 간 상태 공유 (Lifting State Up)

    • 두 개 이상의 컴포넌트가 동일한 데이터를 공유해야 할 때, 상태를 상위 컴포넌트로 올려서 관리하는 방식이다.
    • React는 상태를 부모 컴포넌트에 올려서 하위 컴포넌트에 props로 전달하도록 권장한다.
    • 예제

      function Parent() {
        const [sharedState, setSharedState] = useState("");
        
        return (
          <div>
            <Child1 setSharedState={setSharedState} />
            <Child2 sharedState={sharedState} />
          </div>
        );
      }
      
      function Child1({ setSharedState }) {
        return <input onChange={(e) => setSharedState(e.target.value)} />;
      }
      
      function Child2({ sharedState }) {
        return <p>입력된 값: {sharedState}</p>;
      }
  1. Context API를 통한 상태 관리 (Global State)

    • Context API는 React에서 전역 상태를 관리하기 위해 제공하는 기본 도구이다.
    • 부모 컴포넌트에서 값을 제공(provide)하고, 하위 컴포넌트에서 값을 소비(consume)한다.
    • 상태를 여러 컴포넌트 계층에서 전달할 때 적합하다.
    • 예제

      import React, { createContext, useContext, useState } from "react";
      
      const MyContext = createContext();
      
      function Parent() {
        const [value, setValue] = useState("Hello, Context!");
        
        return (
          <MyContext.Provider value={{ value, setValue }}>
            <Child />
          </MyContext.Provider>
        );
      }
      
      function Child() {
        const { value, setValue } = useContext(MyContext);
        
        return (
          <div>
            <p>{value}</p>
            <button onClick={() => setValue("Updated Value")}>Update</button>
          </div>
        );
      }
  1. 외부 라이브러리를 사용한 상태 관리

    • React 프로젝트가 커지고 상태가 복잡해지면, 외부 상태 관리 라이브러리를 사용하는 것이 효과적이다.

    1) Redux

    • Flux 패턴을 기반으로 하는 대표적인 상태 관리 라이브러리이다.
    • 상태를 단일 저장소(Store)중앙 집중화한다.
    • 액션, 리듀서, 미들웨어 등을 통해 상태를 관리한다.
    • 대규모 애플리케이션에 적합하다.
    • 상태의 흐름을 명확히 추적할 수 있다.
    • 예제

      import { createStore } from "redux";
      import { Provider, useSelector, useDispatch } from "react-redux";
      
      const initialState = { count: 0 };
      
      function reducer(state = initialState, action) {
        switch (action.type) {
          case "INCREMENT":
            return { count: state.count + 1 };
          default:
            return state;
        }
      }
      
      const store = createStore(reducer);
      
      function Counter() {
        const count = useSelector((state) => state.count);
        const dispatch = useDispatch();
        
        return (
          <div>
            <p>Count: {count}</p>
            <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
          </div>
        );
      }
              
      export default function App() {
        return (
          <Provider store={store}>
            <Counter />
          </Provider>
        );
      }

    2) Zustand

    • 가볍고 사용하기 쉬운 상태 관리 라이브러리이다.
    • 상태와 관련된 로직을 단순화한다.
    • 예제

      import create from "zustand";
      
      const useStore = create((set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }));
      
      function Counter() {
        const { count, increment } = useStore();
        
        return (
          <div>
            <p>Count: {count}</p>
            <button onClick={increment}>+</button>
          </div>
        );
      }

    3) Recoil

    • React와 깊이 통합된 상태 관리 라이브러리이다.
    • Atom과 Selector를 통해 상태를 관리한다.
    • 예제

      import { atom, selector, useRecoilState, RecoilRoot } from "recoil";
      
      const countState = atom({
        key: "countState",
        default: 0,
      });
      
      function Counter() {
        const [count, setCount] = useRecoilState(countState);
        
        return (
          <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>+</button>
          </div>
        );
      }
      
      export default function App() {
        return (
          <RecoilRoot>
            <Counter />
          </RecoilRoot>
        );
      }

0개의 댓글