ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React 프론트엔드 개발자의 상태관리 기법과 이유
    Front-end 2024. 5. 23. 09:49
    반응형

    리액트 프론트엔드 개발자의 상태관리 기법과 이유

     

     

    프론트엔드 개발을 하면서 가장 신경이 쓰이는 부분은 아무래도 상태관리일 것이다.

    그 중요도를 아는 만큼 redux, zustand, recoil, mobX, jotai 등 다양한 전역 상태관리 툴이 라이브러리로 제공되어지고 있고, 리액트 자체에서도 context API와 같은 훅을 제공해주기도 한다.

     

    상태 관리라는 말은 애플리케이션의 상태(Data)를 효율적으로 관리하는 것을 말한다.

    UI는 사용자의 interaction에 따라서 동적으로 변해야 하고, 유저의 입력 값, 서버와의 통신 등 다양한 상태를 관리하게 되는데, 적절한 상태 관리 전략을 고려하지 않으면 대규모 애플리케이션의 개발 및 유지보수가 매우 어려워지므로 상태를 효율적으로 관리할 필요성이 있는 것이다.

     


     

    Local state management

    로컬 상태 관리라고 불리는데, 상태를 컴포넌트 내부에서 관리하는 방법이다.

    이름이 낯설어서 그렇지 사실 useState, useReducer 훅을 말하는 것이다.

    React를 주로  사용하는 프론트엔드 개발자라면 useState는 기본 중의 기본 정도로 많이 사용하게 될 것이다.

    오히려 너무 많이 사용해서 문제가 될 정도로 많이 사용하게 될 수도 있다.

    그만큼 간단하게 사용되고, 많이 사용하고 있다.

    또 상태가 복잡하거나 상태 변화 로직이 복잡한 경우에는 reducer 함수와 dispatch를 이용한 useReducer 훅을 사용할 수도 있다.

    이러한 useState, useReducer 훅 또한 상태관리 훅이라고 볼 수 있다.

    이를 사용함으로써 컴포넌트 내부에서만 상태를 관리하므로 불필요한 렌더링을 줄일 수 있고, 사용법이 간단해 소규모 애플리케이션을 만드는데 무리가 없을 것이다.

    그러나 규모가 커지게 되면 상태가 여러 컴포넌트에 분산되게 되어 관리가 어려워지고, 흔히 말하는 prop drilling 문제가 발생할 수 있다.

     

    Context API

    Context API는 React 트리 구조 내에서 상태를 글로벌하게 공유할 수 있는 방법을 제공해준다.

    위에서 말한 prop drilling과 같은 문제가 발생하게 되는 경우 context API를 사용하면 도움을 받을 수 있는 것이다.

    createContext를 이용해서 context를 생성하고, Context.Provider를 이용해서 컴포넌트에 상태를 전달하게 되며, useContext 훅을 이용해서 context의 값을 읽게 된다.

    상태를 전역적으로 공유할 수 있으면서도 비교적 가벼운 설정을 제공하는데, 만약 애플리케이션이 커지면 Context 수가 많아져 관리가 복잡할 수 있으며, 상태가 복잡해지고 액션 관리가 필요해지면 전역상태 관리 라이브러리의 사용을 고려해볼 수 있을 것이다.

     


     

    이제부터는 자주 사용되는 전역관리 라이브러리를 살펴보겠다.

     

    Redux

    우선 npm trend에서도 압도적으로 많이 사용되고 있는 것이 redux 되겠다.

    아무래도 과거부터의 사용량을 보면 어쩔 수 없는 그래프라고 생각된다.

    나는 초기에 Redux의 개념을 익히기가 힘들었는데, 그림을 그려가면서 공부했던 기억이 있다.

    Redux의 주요 개념은 Store, Action, Reducer, Dispatch 이다.

    Redux architecture

    • Store : 애플리케이션의 모든 상태를 보관한다.
    • Action : 상태에 어떤 변화가 일어날지 설명하는 객체이다.
    • Reducer : 액션에 따라 상태를 변경하는 함수이다.
    • Dispatch : 액션을 스토어에 전달하는 함수이다.
    import { createStore } from 'redux';
    
    const initialState = { count: 0 };
    
    function counterReducer(state = initialState, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 };
        case 'decrement':
          return { count: state.count - 1 };
        default:
          return state;
      }
    }
    
    const store = createStore(counterReducer);
    
    store.subscribe(() => console.log(store.getState()));
    
    store.dispatch({ type: 'increment' });
    store.dispatch({ type: 'decrement' });

     

    Redux는 Flux 아키텍처를 사용하며 그 특징은 "단방향 데이터 흐름" 즉, 한 방향으로 데이터가 흘러간다는 것이다.

    그 말은 데이터 변화가 훨씬 더 예측하기 쉽다는 말이 되겠다.

    redux는 react를 위해 나온 것이 아니라 javascript의 전역 개발 라이브러리로 나왔으므로 Vanilla JS, Angular, Vue 등 javascript를 사용하는 곳이라면 어디든 사용할 수 있다.

    요즘은 Redux-toolkit을 많이 사용하는 것 같은데 Redux 사용의 단점이라고 한다면 초기 설정의 복잡함, 그리고 하나를 만들려면 여러가지를 건들여야하는 문제가 있었다.

    redux-toolkit은 그런 점을 보완한 라이브러리이다.

     

    MobX

    반응형 상태 관리 라이브러리이다. 이는 상태관리하는 방식이 Redux와는 다르다.

    MobX는 상태가 변화할 때 자동으로 추적하고 반응하는 방식으로 동작한다.

     

    • Observable State : 상태를 감지 가능한 상태로 만든다.
    • Actions : 상태를 변경하는 함수이다.
    • Computed Values : 상태에 따라 자동으로 계산되는 값이다.
    • Reactions : 상태가 변경될 때 실행되는 함수이다.
    import { observable, action } from 'mobx';
    import { observer } from 'mobx-react';
    
    class CounterStore {
      @observable count = 0;
    
      @action.bound
      increment() {
        this.count += 1;
      }
    
      @action.bound
      decrement() {
        this.count -= 1;
      }
    }
    
    const store = new CounterStore();
    
    const Counter = observer(() => (
      <div>
        <p>{store.count}</p>
        <button onClick={store.increment}>Increment</button>
        <button onClick={store.decrement}>Decrement</button>
      </div>
    ));

     

    예전에 Redux를 사용하다가 힘들어서 MobX를 사용한 적이 있다.

    잠깐 반짝 인기를 끌었다가 다른 라이브러리들이 나오면서 인기가 조금은 식은 듯 한데, 상태가 변화할 때 반응형으로 UI를 업데이트하기 때문에 코드가 간결하고 직관적이며, 특히 상태간의 의존성이 많은 경우 효율적이다.

    또한 장점으로 생각되어지는 상태 변경을 자동으로 감지하고 UI를 업데이트한다는 점이 반대로 예상치 못한 동작을 이해하기 힘들 수도 있을 것이다.

     

    Recoil

    Recoil이 많은 경우 사용 된 이유는 아마 Facebook에서 개발한 상태 관리 라이브러리이기 때문일 것이다.

    Facebook에서 개발한 만큼 React와 자연스럽게 통합되는 장점도 있다.

    이는 Atomic 단위로 상태를 관리하여 상태의 유연성과 재사용성을 높일 수 있다.

     

    • Atoms : 상태의 최소 단위이며, 업데이트가 가능하고 구독 가능하다
    • Selectors :파생 상태를 계산하는 함수로, 다른 atoms나 selector 입력을 받아 파생 값을 반환한다.

    이는 특히 비동기 상태 관리를 쉽게 할 수 있고, 필요할 때만 상태를 구독하는 선택적 상태 구독을 통해 성능을 최적화 시킬 수 있어서  NextJS와도 함께 많이 사용하는 추세를 보인다.

    import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
    
    const countState = atom({
      key: 'countState',
      default: 0,
    });
    
    const doubledCountState = selector({
      key: 'doubledCountState',
      get: ({ get }) => {
        const count = get(countState);
        return count * 2;
      },
    });
    
    function Counter() {
      const [count, setCount] = useRecoilState(countState);
      const doubledCount = useRecoilValue(doubledCountState);
    
      return (
        <div>
          <p>Count: {count}</p>
          <p>Doubled: {doubledCount}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      );
    }

     

    Zustand

    작은 크기와 단순한 API를 제공하는 상태관리 라이브러리이다.

    NPM trands를 보면 Redux를 이길 수는 없지만 가장 빠르게 치고 올라가는 전역상태관리 라이브러리라고 볼 수 있다.

    작은 사이즈와 단순한 API를 제공하므로 경량성이 높고 유연한 설계로 인해서 리액트와의 통합도 자연스럽다.

    최소한의 리렌더링과 최적화를 제공함에 따라 뛰어난 성능을 제공한다.

    그러나 경량성이 높다는 말은 복잡한 상태관리를 위한 기능이 제한적일 수 있다는 말이 되기도 하며, 아직은 redux에 비해서 상대적으로 작은 커뮤니티 생태계를 가지고 있다고 보여진다.

    import create from 'zustand';
    
    // 상태 스토어 생성
    const useStore = create(set => ({
      count: 0,
      increment: () => set(state => ({ count: state.count + 1 })),
      decrement: () => set(state => ({ count: state.count - 1 })),
    }));
    
    // 컴포넌트에서 상태 사용
    function Counter() {
      const count = useStore(state => state.count);
      const increment = useStore(state => state.increment);
      const decrement = useStore(state => state.decrement);
    
      return (
        <div>
          <p>{count}</p>
          <button onClick={increment}>Increment</button>
          <button onClick={decrement}>Decrement</button>
        </div>
      );
    }

     

    프론트엔드 개발자는 애플리케이션의 복잡성과 요구 사항에 따라서 다양한 상태관리 기법들을 선택할 수 있다.

    각 상태관리 기법들은 장단점이 있으며, 특정 상황에 맞는 적절한 도구를 선택하는 것이 중요하다.

    상태 관리 기법을 올바르게 활용하면 애플리케이션의 유지보수성과 확장성을 크게 향상 시킬 수 있을 것이다.

    반응형
Designed by Tistory.