전역상태 무엇을 쓸까? Recoil과 context의 비교

365.48km·2023년 4월 12일
0

전역상태 관리는 왜 필요할까?

-일반적으로 React는 단방향 바인딩으로 부모 컴포넌트에서 자식 컴포넌트로 props(자식요소)를 전달한다. 그러나 컴포넌트를 분리하다보면 컴포넌트 안에 컴포넌트를 여러개 해야하는 경우가 발생하고 이럴때, props-drilling의 문제가 나타난다. 자식 컴포넌트에 props를 계속 전달하면 코드가 지저분해지며, 무리한 리랜더링 문제가 발생할 수 있다. 이러한 경우에 대비해서 전역상태 관리의 필요성이 나타나며 전역상태 라이브러리들이 출시되었다.

물론, 양방향 바인딩인 vue.js의 경우에도 vuex인 store인 라이브러리들이 존재한다. 따라서, 양방향이든 단방향이든 문제라기보다는 상태 변화를 효과적이고 적절하게 필요한 컴포넌트에서 사용하는 것이 중요하다.

React에서 대표적인 전역상태관리로 context, redux와 가장 최근에 출시된 recoil이 있다. 여기에서는 useContextRecoil에 대해서만 다루겠다.

Context

useContext로 사용하는 React의 훅이다. createContext 함수를 사용하여 context 객체를 생성하고 Provider 컴포넌트를 통해 context값을 하위 컴포넌트로 전달할 수 있다. seContext 훅 을 사용하여 해당 컨텍스트의 값을 받아와서 사용할 수 있습니다.

Recoil

Recoil은 Facebook에서 개발한 상태 관리 라이브러리입니다. useState와 비슷한 문법을 사용하면서도 전역 상태 관리를 할 수 있습니다. Atom이라는 단위로 상태를 정의하고, selector를 사용하여 상태 값을 가공하거나 다른 Atom의 값을 사용할 수 있습니다.

언제 전역상태 관리가 필요할까?🤔

아마 대표적으로는 로그인과 같은 인증상태관리 또는 테마에 대해서 전역상태관리가 필요할 것이다. 이때, 개인적인 생각으로는 Recoil이 더 적합(?)한 것같다. 왜냐하면 Recoil은 상태 변경 시 변경된 상태에 대한 업데이트가 발생하면 자동으로 리랜더링이 된다.

이는 useState와 같은 다른 상태 관리 라이브러리와 비교했을 때도 마찬가지이지만, useContext와 같은 Context API를 사용한 상태 관리보다 더욱 효율적인 렌더링을 가능하게 한다.

이는 Recoil이 내부적으로 상태 값의 의존성 그래프(dependency graph)를 추적하고, 해당 그래프에서 변경된 부분만 리렌더링하도록 최적화하기 때문이다.
이를 통해, 관련 없는 부분까지 리렌더링하는 것을 방지하고, 불필요한 성능 저하를 막을 수 있습니다.

또한, Recoil은 성능 최적화를 위해 Suspense와 같은 React의 다른 기능들과도 연동이 가능합니다. 이를 통해, 데이터가 로드되지 않은 상태에서 렌더링하는 것을 방지하고, 로딩 중인 상태를 처리할 수 있다.

따라서, Recoil은 내부적으로 상태 값의 의존성 그래프를 추적하고, 이를 최적화함으로써 더욱 효율적인 렌더링을 가능하게 합니다. 이러한 특징은 useContext와 같은 다른 상태 관리 방법보다 Recoil을 더욱 성능적으로 효율적인 상태 관리 방법으로 만든다.

그렇다면 언제 Context를 쓰고 언제 Recoil을 사용하는 것이 좋을까? 🤔

Context와 Recoil은 React에서 상태 관리를 위한 라이브러리이며, 둘 다 컴포넌트 간에 상태를 전달하고 공유하는 방법을 제공한다.

useContextRecoil은 전역 상태 관리이지만 방식에서 차이가 있다. 만약 간단한 상태 값의 전달이 필요하다면 useContext를 사용하는 것이 좋고, 전역 상태를 관리하거나 상태 값이 복잡한 경우에는 Recoil을 사용하는 것이 좋다.

Recoil 예시

import { atom } from 'recoil';

export const isLoggedInState = atom({
  key: 'isLoggedIn',
  default: false,
});

위에서 정의한 전역 상태를 컴포넌트에서 사용하여 로그인 상태를 관리할 수 있습니다.

import React from 'react';
import { useRecoilState } from 'recoil';
import { isLoggedInState } from './recoil/atoms';

function LoginButton() {
  const [isLoggedIn, setIsLoggedIn] = useRecoilState(isLoggedInState);

  function handleLogin() {
    setIsLoggedIn(true);
  }

  function handleLogout() {
    setIsLoggedIn(false);
  }

  return (
    <>
      {isLoggedIn ? (
        <button onClick={handleLogout}>로그아웃</button>
      ) : (
        <button onClick={handleLogin}>로그인</button>
      )}
    </>
  );
}
  • 위 코드에서는 isLoggedInState를 useRecoilState 훅을 통해 가져와서, 해당 상태가 변경될 때마다 컴포넌트가 자동으로 리렌더링됩니다.
  • 이는 Recoil이 내부적으로 상태 값의 의존성 그래프를 추적하고, 해당 그래프에서 변경된 부분만 리렌더링하도록 최적화하기 때문입니다.
  • 따라서, isLoggedInState 값이 변경될 때마다 컴포넌트가 불필요하게 리렌더링되지 않고, 효율적인 렌더링이 가능해집니다.
  • 반면, useContext를 사용한 상태 관리는 이러한 최적화가 자동으로 이루어지지 않기 때문에, Memoization 등의 기술을 활용하여 최적화를 수동으로 구현해야 합니다.

Context 예시

import React, { useContext, useState } from "react";

// createContext 함수로 컨텍스트 생성
const CounterContext = React.createContext();

function CounterProvider(props) {
  const [count, setCount] = useState(0);

  // 상태와 상태변경 함수를 value에 포함시켜 Provider 반환
  return (
    <CounterContext.Provider value={{ count, setCount }}>
      {props.children}
    </CounterContext.Provider>
  );
}

function Counter() {
  // useContext를 이용해 컨텍스트에서 상태와 상태변경 함수를 가져옴
  const { count, setCount } = useContext(CounterContext);

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <Counter />
    </CounterProvider>
  );
}

export default App;

createContextCounterContext라는 컨텍스트를 생성하고, CounterProvider 함수에서 상태와 상태변경 함수를 관리합니다. CounterProvider 함수는 createContext를 이용해 생성한 CounterContext.Provider를 반환하며, 이를 이용해 Counter 컴포넌트에서 useContext를 통해 상태와 상태변경 함수를 가져와 사용합니다. 상태변경 함수를 호출할 때마다 Counter 컴포넌트가 리렌더링됩니다.

Context의 장점과 단점

장점

  • 전역 상태를 관리하기 편리하며, 중복 코드를 줄일 수 있다
  • 상태를 주입하고 받을 컴포넌트의 계층 구조가 단순할 경우 구현하기 쉽다.
  • 비교적 러닝커브가 길고 복잡한 Redux와 같은 상태 관리 라이브러리를 사용하지 않고도 쉽게 구현할 수 있습니다.

단점

  • Context의 상태 변화가 일어날 때마다 해당 컴포넌트와 그 자식 컴포넌트들이 모두 리렌더링되므로, 성능 이슈가 발생할 수 있습니다.
    - 사실 이 부분때문에 오늘의 Recoil와 같이 설명하게 되었다.
  • Context를 사용하기 위해서는 상태를 변경하는 로직을 직접 구현해야 하므로 복잡한 로직에 적용하기 어렵다.
  • 상태를 관리하기 위한 코드가 매우 많아질 수 있다.

Recoil의 장점과 단점

장점

  • 상태 간의 의존성 그래프를 쉽게 관리할 수 있습니다.
  • 상태 변화에 대한 최적화가 내장되어 있어, 상태 변화가 발생할 때마다 전체 컴포넌트를 다시 렌더링하지 않아도 됩니다.
  • 대규모 애플리케이션에서 사용하기 적합하며, 다양한 상황에서 사용 가능한 높은 유연성을 제공합니다.

단점

  • 초기 구현이 어려울 수 있습니다.
  • 상태를 관리하기 위해 사용해야 하는 코드가 많아질 수 있습니다.

결론적으로

  • Context는 단순한 상태 전달이나 트리 상태 전달을 위한 유용한 패턴을 제공하며, 작은 규모의 애플리케이션에서는 적합합니다.
  • 반면, Recoil은 대규모 애플리케이션의 복잡한 상태 관리를 위해 사용할 수 있습니다. 상황에 따라 두 가지 방법을 적절하게 조합하여 사용할 수도 있습니다.
profile
이게 마즐까?

0개의 댓글