본문 바로가기
React/기초

React 상태 관리 Recoil (feat. useState와 차이점)

by 뿌비 2024. 10. 8.
728x90
그동안 리액트 상태 관리는  useState로 했었는데 이번에 개인 프로젝트를 하면서 Recoil를 써보려고 차근차근 알아가는 중이다. 

Recoil 이란? 

Recoil은 페이스북에서 만든 React 전역 상태관리 라이브러리이다.

Recoil 설치 및 사용법

1. Recoil 설치

// npm 사용 시
npm install recoil

// yarn 사용 시
yarn add recoil

2. RecoilRoot

  • Recoil은 React의 상태 관리 라이브러리이기 때문에 (index.js 또는 App.js 파일에서) RecoilRoot로 애플리케이션 전체를 감싸야한다.
  • RecoilRoot는 상태 관리를 위한 컨텍스트를 제공한다.
// index.js 
import "antd/dist/antd.css"; // Ant Design 기본 스타일
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { RecoilRoot } from "recoil";
import { BrowserRouter } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <React.StrictMode>
    <BrowserRouter basename="/프로젝트경로">
      <RecoilRoot>  -> 여기
        <App />
      </RecoilRoot>
    </BrowserRouter>
  </React.StrictMode>
);

3. Atom 생성

근데 이름이 왜 Atom 일까? (갑자기 궁금증이 들어 찾아보았다.)
Recoil에서 사용하는 Atom이라는 용어는 원자(Atom)라는 개념에서 유래되었다고 한다.
원자(Atom)는 시스템의 기본 단위로, 독립적이고 변하지 않는 상태를 표현하며, 이를 통해 상태 관리를 간결하고 효율적으로 할 수 있도록 설계되었다.
  • Recoil에서는 Atom을 사용하여 상태를 정의한다. 이 Atom은 전역 상태의 일종으로, 애플리케이션의 여러 컴포넌트에서 공유될 수 있다.
  • Atom은 별도의 파일로 정의하는 것이 일반적이다.
import { atom } from 'recoil';

export const textState = atom({
  key: 'textState', // 고유한 키
  default: '', // 초기값
});
Atom key와 default 역할

1. 고유한 키 (key)
☞ key는 atom의 고유 식별자이다.
☞ Recoil이 여러 atom을 식별하고 관리하는 데 사용된다.
☞ 문자열로 제공되며, 애플리케이션 내에서 유일해야 한다. 동일한 key를 가진 atom이 두 개 이상 존재할 수 없기 때문에, 키는 고유성을 유지해야 한다.

2. 초기값 (default)
☞ default는 atom의 초기 상태를 정의한다.
☞ 컴포넌트가 이 atom을 처음 구독할 때, 이 초기값이 사용된다.
☞ 기본적으로 어떤 데이터 유형이든 가능하며, 문자열, 숫자, 객체, 배열 등 다양한 형태의 값을 지정할 수 있다.

4. Atom  구독, 상태 읽기 및 업데이트

컴포넌트에서 atom을 구독하여 상태를 가져오고, 상태를 변경할 수 있다.

Recoil에서는 useRecoilState, useRecoilValue, 또는 useSetRecoilState와 같은 훅을 사용하여 atom에 접근한다.

// useRecoilState를 사용하여 atom의 값을 읽고 업데이트하는 방법
import React from 'react';
import { useRecoilState } from 'recoil';
import { textState } from './atom을 정의한 파일경로'; // atom을 가져옴

function TextInput() {
  const [text, setText] = useRecoilState(textState); // atom 구독

  const onChange = (event) => {
    setText(event.target.value); // 상태 업데이트
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <p>You typed: {text}</p>
    </div>
  );
}

export default TextInput;

5. Selector 사용하기 (옵션)

Recoil에서는 selector를 사용하여 파생된 상태를 생성할 수 있다.

selector는 atom에서 계산된 값을 반환하는 함수이다.

// 아톰 정의
import { selector } from 'recoil';
import { textState } from './state';

export const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

// 컴포넌트에서 selector를 useRecoilValue로 가져와 사용할 수 있다
import React from 'react';
import { useRecoilValue } from 'recoil';
import { charCountState } from './state';

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <p>Character Count: {count}</p>;
}

export default CharacterCount;

Recoil에서 상태 관리할 때 주로 사용하는 훅

1. useRecoilState

  • atom의 상태를 읽고, 업데이트할 수 있는 훅이다.
  • 상태를 읽고, 그 상태를 직접 수정해야 하는 경우에 사용한다.
// text는 atom의 현재 상태 값을 나타내며, setText를 사용하여 상태를 업데이트 한다
const [text, setText] = useRecoilState(textState);

2. useRecoilValue

  • atom이나 selector의 현재 값을 읽기 위한 훅이다. 상태를 읽기만 하고 업데이트할 필요가 없는 경우에 사용된다.
  • 상태를 조회하여 UI에 표시하거나 다른 계산에 사용할 때 유용하다. 
// text는 atom의 현재 상태 값을 가져오며, 상태를 업데이트하는 기능은 없다.
const text = useRecoilValue(textState);

3. useSetRecoilState

  • atom의 상태를 업데이트하기 위한 전용 훅이다.
  • 상태를 읽지 않고, 오로지 업데이트만 필요할 때 유용하다.
// setText를 사용하여 atom의 상태를 업데이트할 수 있다.
const setText = useSetRecoilState(textState);

4. useRecoilCallback

  • Recoil 상태를 읽고 업데이트하는 콜백 함수를 만드는 데 사용된다.
  • 주로 컴포넌트 외부에서 Recoil 상태에 접근하거나, 비동기 작업이 필요할 때 유용하다.
/**
 * snapshot: 현재 상태의 스냅샷을 나타내며, 상태를 읽을 때 사용된다. 
 * snapshot.getLoadable(textState).contents
 * - snapshot.getLoadable은 주어진 리코일 상태(textState)의 값을 가져오는 함수이다. 
 * - 이 값은 Loadable 객체 형태로 반환되며,
 * - Loadable은 리코일 상태가 비동기일 경우 상태를 loading, hasValue, hasError 중 하나로 표시한다.
 * - .contents는 그 상태의 실제 값을 얻기 위한 속성이다.아래의 코드에서 textState의 현재 값을 읽는다.
 * set: Recoil 상태를 업데이트하는 함수로, textState의 값을 'new value'로 변경 한다.
 * 
 * 동작 요약
 * snapshot: 현재 textState의 값을 가져와서
 * set: textState의 값을 'new value'로 업데이트
 */
const callback = useRecoilCallback(({ snapshot, set }) => () => {
  const currentValue = snapshot.getLoadable(textState).contents;
  set(textState, 'new value');
});

5. useRecoilTransaction_UNSTABLE

  • 여러 atom의 상태를 동시에 업데이트할 수 있는 기능을 제공한다. (이 훅은 아직 안정화되지 않았음)
/**
 * set(textState, newValue): textState의 값을 newValue로 설정 한다.
 * set(anotherState, newValue * 2): anotherState의 값을 newValue의 두배로 설정 한다.
 * 
 * 동작 요약
 * newValue를 받아서 textState와 anotherState 두 상태를 각각 newValue와
 * newValue * 2로 동시에 업데이트한다.
 */
const setValue = useRecoilTransaction_UNSTABLE(({ set }) => (newValue) => {
  set(textState, newValue);
  set(anotherState, newValue * 2);
});
※ 간단 요약 
useRecoilState: 상태를 읽고 업데이트
useRecoilValue: 상태를 읽기만 할 때
useSetRecoilState: 상태를 업데이트만 할 때
useRecoilCallback: 컴포넌트 외부에서 상태를 읽고 업데이트하는 콜백 생성 
useRecoilTransaction_UNSTABLE: 여러 atom의 상태를 동시에 업데이트

useState와 Recoil의 차이점

구분  useState Recoil 
상태 관리 범위 - 상태는 해당 컴포넌트 내부에서만 유효
- 컴포넌트가 다시 렌더링될 때, 그 컴포넌트의 상태만 영향을 받음
- 상태는 애플리케이션의 여러 컴포넌트에서 공유 가능
- 여러 컴포넌트가 동일한 atom을 구독하며, atom의 상태가 변경되면 이를 사용하는 모든 컴포넌트가 자동으로 업데이트됨
상태 업데이트 방식 - 상태를 업데이트하려면 해당 컴포넌트의 상태 업데이트 함수를 호출해야 함
- 상태를 가진 컴포넌트만 리렌더링됨
- atom의 상태는 useRecoilState를 사용하여 업데이트 가능
- 상태 변경이 발생하면 해당 atom을 참조하는 모든 컴포넌트가 리렌더링됨
파생 상태 - 파생 상태를 만들기 위해 상태를 기반으로 새 상태를 계산해야 함
- useEffect와 함께 사용 가능
- selector를 사용하여 atom의 값을 기반으로 한 파생 상태를 쉽게 생성 가능
- selector는 자동으로 업데이트되며, 이를 사용하는 컴포넌트가 리렌더링됨
상태 관리의 복잡성 - 상태가 간단할 경우 useState가 직관적이고 간편함
- 복잡한 상태 구조를 관리할 때 여러 개의 상태 변수를 만들어야 하므로 코드가 복잡해질 수 있음
- 복잡한 상태를 구조화된 방식으로 관리 가능
- atom과 selector를 조합하여 복잡한 상태 구조를 쉽게 처리할 수 있음
전역 상태 관리 - 전역 상태를 관리하려면 Context API나 Redux 등의 다른 상태 관리 솔루션 필요 - 기본적으로 전역 상태 관리 기능을 제공
- 여러 컴포넌트 간의 상태 공유가 용이함
비동기 처리 - 비동기 데이터 처리와 함께 상태를 업데이트하려면 useEffect와 결합해야 함 - selector를 통해 비동기 요청을 쉽게 처리 가능
- selector에서 비동기 함수를 반환하여 상태를 관리할 수 있음

이해한 대로 적어보는 useState와 Recoil의 장단점과 코드의 차이 

useState

장점

  • 상태가 해당 컴포넌트에서만 유효하므로 직관적이고 간편함
  • 다른 컴포넌트의 영향을 받지 않음

단점

  • 동일한 초기값을 가진 상태를 여러 컴포넌트에 중복으로 정의해야 함
// 예시 : default 값이 "" 인 상태가 필요한 상황이라고 했을때 

// 상태 정의
// aa.js
const [test, setTest] = useState("");

// bb.js
const [test, setTest] = useState("");

// 값 읽기 
console.log(test) // test 변수 사용(초기값은 "")

//초기값을 다르게 설정하려면
const [test, setTest] = useState("apple"); // test 초기값 : apple

// aa.js 값 변경 
setTest("melon"); // 값 변경 후 test값 : melon

// bb.js 
// test의 값은 여전히 "", 왜? : 상태는 해당 컴포넌트 내부에서만 유효하기때문에

 

Recoil

장점

  • 상태를 여러 컴포넌트에서 공유할 수 있어, 상태 정의의 중복을 방지할 수 있음
  • 중앙 집중화된 관리로 유지보수성이 높음

단점

  • atom 값 변경 시 모든 참조 컴포넌트의 상태가 함께 변경됨. 따라서 의도치 않은 변경에 주의가 필요함
// 예시 : default 값이 "" 인 상태가 필요한 상황이라고 했을때 

// 1. atom을 모두 정의해놓을 RecoilStatus.js 만들고 그 안에 atom 정의
export const commonEmptyStringState = atom({
 // key는 고유키로 설정하면되고 우리가 가져다 쓰지 않음(Recoil이 여러 atom을 식별,관리하는 데 사용)
  key: "commonEmptyString", // 고유한 식별자
  default: "", // 초기값: 빈 문자열, 숫자등 필요한 타입 작성
});

// 각 js에서 atom 구독
// aa.js 
import { useRecoilState} from "recoil";
import { commonEmptyStringState} from "/RecoilStatus.js경로";
const MainHome = () => {
  const [text, setText] = useRecoilState(commonEmptyStringState); // atom을 구독
}

// bb.js
import { useRecoilState} from "recoil";
import { commonEmptyStringState} from "/RecoilStatus.js경로";
const MainHome = () => {
  const [text, setText] = useRecoilState(commonEmptyStringState); // atom을 구독
}


// 값 읽기 
console.log(test) // test 변수 사용(초기값은 "")

//초기값을 다르게 설정하려면 atom 추가 or 변경
export const commonEmptyStringState = atom({
  key: "commonEmptyString", // 고유한 식별자
  default: "aa", 
});
console.log(test) // test 초기값 : aa

// aa.js 값 수정
setText("apple");

/* 
* 수정 후
* aa.js text 값 : "apple"
* bb.js text 값 : "apple"
* 왜? : atom의 상태가 변경되면 이를 사용하는 모든 컴포넌트가 자동으로 업데이트됨
*/
728x90