Programming/react

[react] 상태 관리 도구, Recoil 개념

x-coder 2025. 4. 20. 15:48

Hello. #{Somebody}

React 애플리케이션 상태 관리를 효율적으로 하기

 

안녕하세요! React 개발, 재미있게 하고 계신가요? 😄

리액트로 애플리케이션을 만들다 보면, 컴포넌트들이 서로 데이터를 주고받고 상태를 공유해야 할 때가 정말 많습니다. 처음에는 useState나 useContext로 충분하다고 느끼지만, 애플리케이션이 복잡해질수록 상태 관리가 점점 어려워지고 'props drilling'이라는 문제에 부딪히기도 하죠.

이럴 때 빛처럼 등장하는 라이브러리들이 있는데, Recoil도 그중 하나입니다. Facebook에서 만든 Recoil은 React 애플리케이션의 상태 관리를 효율적으로 할 수 있도록 도와주는 도구입니다.

 

Recoil, 상태 관리가 왜 필요한가?

React는 컴포넌트 기반으로 UI를 구축합니다. 각 컴포넌트는 자체적인 상태를 가질 수 있고, 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 데이터를 전달할 수 있습니다. 하지만 애플리케이션의 규모가 커지고, 멀리 떨어져 있는 컴포넌트끼리 상태를 공유해야 할 때는 어떻게 해야 할까요?

가장 간단한 방법은 공유해야 하는 상태를 공통 부모 컴포넌트까지 끌어올린 다음, 필요한 자식들에게 props로 계속 내려주는 것입니다. 이것을 'props drilling'이라고 부릅니다. 마치 깊은 곳까지 물을 내리기 위해 파이프를 여러 개 연결하는 것에 비유할 수 있죠.

이 방법은 컴포넌트 트리가 깊어질수록 코드를 이해하기 어렵게 만들고, 중간에 필요 없는 컴포넌트들에게까지 상태를 전달해야 하는 비효율성을 초래합니다. 바로 이런 문제를 해결하기 위해 전역 상태 관리 라이브러리들이 등장했답니다. Redux, MobX, 그리고 오늘 우리가 만날 Recoil처럼요! Recoil은 React스럽고 보일러플레이트가 적다는 장점을 가지고 있습니다.

 

Recoil의 핵심, atom이란 무엇인가?

Recoil에서 atom은 '상태(State)의 가장 작은 단위'라고 생각하시면 됩니다. 마치 원자(atom)처럼 더 이상 쪼갤 수 없는 상태의 조각이죠.

각 atom은 고유한 키(key)를 가지고 있으며, 어떤 기본값(default value)으로 시작할지를 정의합니다. 이 atom 안에 특정 상태 값이 저장되고, 여러분의 React 컴포넌트들은 이 atom을 '구독'하게 됩니다.

컴포넌트가 atom을 구독하고 있다가, 만약 해당 atom의 상태 값이 변경되면, 그 atom을 구독하고 있는 컴포넌트들만! 효율적으로 다시 렌더링됩니다. 필요한 컴포넌트만 업데이트되기 때문에 전체 애플리케이션의 성능에도 좋은 영향을 줄 수 있습니다.

쉽게 비유하자면, atom은 특정 정보를 담고 있는 '작은 라디오 채널'과 같습니다. 정보를 받고 싶은 컴포넌트들은 이 채널을 '구독'하면 되죠. 채널에서 새로운 정보(상태 변경)가 방송되면, 구독하고 있는 컴포넌트들만 그 정보를 듣고 반응하는 것입니다.

 

atom은 어떻게 만들까? - 첫 번째 코드!

atom을 만드는 것은 매우 간단합니다. Recoil에서 제공하는 atom 함수를 호출하고, 몇 가지 설정을 해주면 됩니다.

필수적으로 필요한 설정은 딱 두 가지입니다.

  1. key: 이 atom을 식별할 수 있는 고유한 문자열 키입니다. 애플리케이션 전체에서 중복되지 않도록 조심해야 합니다.
  2. default: 이 atom이 처음 만들어졌을 때 가질 기본값입니다.

아래는 간단한 카운터 애플리케이션에서 사용할 숫자 상태를 담는 atom의 샘플 소스입니다.

// src/recoil/counterState.js 파일 같은 곳에 정의하면 좋습니다.
import { atom } from 'recoil';

// 'counterState'라는 고유한 키를 가진 atom을 정의합니다.
// 기본값은 0으로 설정했습니다.
export const counterState = atom({
  key: 'counterState', // ⭐️ 다른 atom들과 겹치지 않도록 고유해야 합니다!
  default: 0, // ⭐️ 이 atom이 처음 가질 기본값입니다.
});

// 문자열 상태를 저장하는 atom 예시
export const textState = atom({
  key: 'textState',
  default: '',
});

// 객체 상태를 저장하는 atom 예시
export const userState = atom({
  key: 'userState',
  default: { name: '', age: 0 },
});

보시는 것처럼 atom 함수에 객체를 넘겨주고 key와 default 값을 설정해주면 끝입니다! 참 쉽죠? 이렇게 만들어진 atom들은 이제 우리의 React 컴포넌트 어디서든 접근해서 사용하고 변경할 수 있게 됩니다.

 

atom을 컴포넌트에서 사용하기: Recoil Hooks

만들어진 atom을 React 컴포넌트에서 사용하려면 Recoil에서 제공하는 특별한 훅(Hook)들을 사용해야 합니다.

마치 useState나 useEffect처럼요! Recoil에서 atom의 상태를 읽거나 변경하기 위해 주로 사용되는 훅은 세 가지가 있습니다.

  • useRecoilState: useState와 가장 유사합니다. atom의 현재 값과 그 값을 업데이트할 수 있는 함수를 배열 형태로 반환합니다. 상태를 읽고 변경해야 할 때 사용합니다.
  • useRecoilValue: atom의 현재 값만 읽어올 때 사용합니다. 상태를 변경할 필요 없이 단순히 표시만 할 때 사용하면 코드를 더 간결하게 만들 수 있습니다.
  • useSetRecoilState: atom의 값을 업데이트할 수 있는 함수만 필요할 때 사용합니다. 상태 값을 읽을 필요 없이 특정 이벤트(예: 버튼 클릭)에 따라 상태를 변경하기만 할 때 사용하면 좋습니다. 이 훅은 해당 컴포넌트가 상태 값 변경에 의해 리렌더링되는 것을 방지하여 성능 최적화에 도움이 될 수 있습니다 (단, useRecoilValue와 함께 사용하지 않는 경우).

이 세 가지 훅의 특징을 표로 정리해보면..

훅 이름사용 용도주로 사용되는 경우반환 값
useRecoilState 읽기 & 쓰기 상태 값을 읽고, 사용자의 인터랙션 등으로 변경해야 할 때 [상태 값, 상태 업데이트 함수]
useRecoilValue 읽기 전용 상태 값을 가져와 화면에 표시하기만 하면 될 때 상태 값
useSetRecoilState 쓰기 전용 상태 값 변경 함수만 필요하고 상태 값을 읽을 필요가 없을 때 상태 업데이트 함수 (setter)
상황에 맞는 훅을 선택하여 사용하면 Recoil 상태 관리를 더 효율적으로 할 수 있습니다.

 

atom 사용 예제: 간단한 카운터 만들기

앞에서 만든 counterState atom을 사용하여 간단한 카운터 컴포넌트들을 만들어 보겠습니다.

먼저 Recoil을 사용하려면 애플리케이션의 최상위 컴포넌트(일반적으로 App.js 또는 index.js)를 RecoilRoot로 감싸야 합니다.

그래야 하위 컴포넌트들이 Recoil 상태에 접근할 수 있게 됩니다.

// src/index.js 또는 App.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // 애플리케이션을 RecoilRoot로 감싸줍니다.
  <RecoilRoot>
    <App />
  </RecoilRoot>
);

자, 이제 atom을 사용하는 컴포넌트들을 만들어 봅시다.

counterState atom 정의 (앞에서 만들었던 것)

// src/recoil/counterState.js
import { atom } from 'recoil';

export const counterState = atom({
  key: 'counterState',
  default: 0,
});

카운터 값을 표시하는 컴포넌트 (읽기 전용)

counterState 값을 읽어와서 화면에 표시하는 컴포넌트입니다. 값만 필요하므로 useRecoilValue 훅을 사용하겠습니다.

// src/components/CounterDisplay.js
import React from 'react';
import { useRecoilValue } from 'recoil'; // useRecoilValue 훅 임포트
import { counterState } from '../recoil/counterState'; // atom 임포트

function CounterDisplay() {
  // counterState의 현재 값을 읽어옵니다.
  const count = useRecoilValue(counterState);

  return (
    <div>
      <h2>현재 카운트: {count}</h2>
    </div>
  );
}

export default CounterDisplay;

카운터 값을 변경하는 컴포넌트 (쓰기 전용)

카운터 값을 증가시키는 버튼을 가진 컴포넌트입니다. 여기서는 상태 값을 읽을 필요 없이 변경 함수만 있으면 되므로 useSetRecoilState 훅을 사용해 보겠습니다.

// src/components/CounterButtons.js
import React from 'react';
import { useSetRecoilState } from 'recoil'; // useSetRecoilState 훅 임포트
import { counterState } from '../recoil/counterState'; // atom 임포트

function CounterButtons() {
  // counterState 값을 변경하는 함수(setter)만 가져옵니다.
  const setCounter = useSetRecoilState(counterState);

  // 버튼 클릭 시 counterState 값을 1 증가시키는 함수
  const incrementCounter = () => {
    // 이전 값에 접근하여 업데이트할 수 있습니다.
    setCounter(prevCount => prevCount + 1);
  };

  return (
    <div>
      <button onClick={incrementCounter}>카운트 증가</button>
    </div>
  );
}

export default CounterButtons;

두 컴포넌트를 사용하는 부모 컴포넌트

CounterDisplay와 CounterButtons 컴포넌트는 서로 부모-자식 관계가 아니더라도 동일한 counterState atom을 사용하기 때문에 상태를 공유하고 동기화할 수 있습니다.

// src/App.js
import React from 'react';
import CounterDisplay from './components/CounterDisplay';
import CounterButtons from './components/CounterButtons';

function App() {
  return (
    <div>
      <h1>Recoil Atom 예제</h1>
      {/* CounterDisplay는 counterState 값을 읽어와서 표시 */}
      <CounterDisplay />
      {/* CounterButtons는 counterState 값을 변경 */}
      <CounterButtons />
      <p>
        위 두 컴포넌트는 서로 직접적인 데이터 전달 없이 Recoil의 counterState atom을 통해 상태를 공유합니다.
      </p>
    </div>
  );
}

export default App;

이렇게 구성하면 CounterButtons에서 '카운트 증가' 버튼을 누르면 counterState의 값이 변경되고, 이 변경을 구독하고 있는 CounterDisplay 컴포넌트만 다시 렌더링되어 업데이트된 카운트 값을 보여주게 됩니다.

App 컴포넌트나 다른 컴포넌트는 상태 변화에 영향을 받지 않죠!

 

atom 사용의 장점들

Recoil의 atom을 사용하면 다음과 같은 좋은 점들이 있습니다.

  • 직관적인 상태 관리: 각 atom이 독립적인 상태 단위를 나타내므로, 어떤 상태가 어디에 저장되어 있는지 파악하기 쉽습니다.
  • 유연한 접근: 컴포넌트 트리 어디에 있든 atom에 직접 접근하여 상태를 읽고 쓸 수 있습니다. Props drilling에서 벗어날 수 있습니다!
  • 효율적인 업데이트: atom 상태가 변경되면, 해당 atom을 구독하고 있는 컴포넌트들만 리렌더링됩니다. 불필요한 리렌더링을 줄여 애플리케이션 성능을 개선할 수 있습니다.
  • React 친화적: Recoil은 React 훅(Hooks) 패턴을 기반으로 만들어져서 React 개발자에게 매우 익숙하고 자연스럽습니다.
  • 보일러플레이트 감소: 다른 전역 상태 관리 라이브러리에 비해 설정이나 코드가 간단한 편입니다.

 

Recoil의 atom은 React 애플리케이션에서 상태 관리를 시작하는 가장 기본적이면서도 강력한 도구입니다. 마치 작은 블록 하나하나처럼, atom들을 조합하고 필요에 따라 selector(이건 다음 기회에 이야기해 볼 Recoil의 또 다른 핵심 개념입니다!)와 함께 사용하면 복잡한 상태 로직도 훨씬 간결하고 효율적으로 관리할 수 있게 됩니다.

오늘 우리는 atom이 무엇인지, 왜 필요한지, 그리고 useRecoilState, useRecoilValue, useSetRecoilState 훅을 사용하여 어떻게 컴포넌트에서 활용하는지 간단한 예제를 통해 살펴보았습니다.

Recoil은 atom 외에도 상태 변환, 비동기 처리 등을 위한 다양한 기능을 제공합니다.

하지만 그 시작은 언제나 atom이라는 것을 기억해야 합니다!

 

 

 

Bye. #{Somebody}