상태관리 라이브러리 Recoil

September 25, 2023

recoil_logo

안녕하세요 개발팀 최호경입니다.

이번에 recoil을 주제로 컨텐츠를 만들게 되면서, 그 동안 궁금했던 상태관리 라이브러리에 대해 공부할 수 있는 기회가 되었어요.

그래서 제가 이해한 내용을 바탕으로 설명해드리면 좋을 것 같아, 이렇게 글로 남겨보려고 합니다.

바로 시작하겠습니다!


상태관리의 등장 배경

사용자와의 상호작용이 필요없이 정보만 보여주는 정적인 웹사이트에서는 서버에서 보내주는 데이터를 그대로 보여주기만 하면 되었습니다. 대표적인 예시는 ‘기업 홍보 웹사이트’ 등이 있죠.

하지만 동적인 웹사이트에서는 사용자의 입력을 받아, 그때그때 마다 또는 사용자별로 서로 다른 화면을 동적으로 보여줘야 했죠. 결국 사용자와의 상호작용으로 인해 데이터가 수시로 변경됩니다. 따라서 이 데이터의 변화를 추적하고 관리할 필요성이 생겼습니다.

상태관리는 영문으로 직역하면 ‘State Management’죠? 우리는 데이터를 변수에 담아서 사용하는데, 변수에 담긴 데이터를 변경될 수 있습니다. 그래서 ‘상태’로서 이 변수가 지금 어떤 데이터를 갖고 있는지 알아야 우리가 원하는 대로 화면에서 보이게 할 수 있죠.


상태관리의 의미

따라서 프론트엔드에서 ‘상태관리’는 2가지 의미를 갖습니다.

  1. UI/UX에 맞게 필요한 데이터를 관리하고 기능을 구현하는 것
  2. 서버와 클라이언트가 통신하는 데이터의 변화 상태를 추적 및 관리하는 것

쉽게 말해, 일단 화면에서 잘 보이고 동작하게 만들고, 사용자로 인해 변경되는 데이터도 잘 보이도록 관리하는 것입니다.

전역상태관리의 등장

프론트엔드에서는 화면 및 로직에서 반복되는 요소들을 재사용하기 위해 컴포넌트(Component)를 사용합니다.

특정 기능을 하는 코드들을 모듈화해서 만들어 놓은 것을 가져다가 바로 쓸 수 있게 되었죠.

이때 컴포넌트 내부에서 사용하는 상태는 state 이고, 자신의 부모 컴포넌트에서 전달받은 상태를 props라고 합니다. 이렇게 부모로부터 전달받은 데이터를 활용할 수 있어서, 서로 다른 컴포넌트끼리 데이터를 공유하며 기능을 구현할 수 있게 되었습니다.

그런데 이 props는 부모-자식 관계에서만 전달이 가능했기 때문에, 컴포넌트 구조가 크고 복잡해짐에 따라, 같은 데이터를 10개 이상의 props 연결로 전달해야 하는 현상도 발생하게 되었습니다. 이를 props drilling이라고 하는데요. props 전달하는 과정에서 불필요한 request와 rendering이 발생하여 리소스가 낭비될 수 있는 것이죠.

최상위 부모 컴포넌트 -> 부모 -> … -> 자식 -> 실제 데이터 사용 컴포넌트

따라서 props가 비효율적으로 전달되는 방식을 보완하기 위해서 전역 상태관리가 나오게 되었습니다.

외부의 중앙집중식의 저장소를 만들어서, 거기에 데이터를 담습니다. 그리고 필요할 때 거기에서 찾아서 사용하는 거죠.


다양한 상태관리 라이브러리

어떻게 하면 이 전역상태관리를 효율적으로 할지에 대해 많은 개발자들이 고민하며, 다양한 특징의 라이브러리들이 많이 생겨나게 되었습니다.

state_management_download_number state_management_statistics

주요 특징

  1. atomic : 각 상태를 독립적인 단일 단위로 구분하여 공유함 - ex) recoil, jotai
  2. context : context provider 내부에서 상태를 공유해서 사용 - ex) context api
  3. flux : store, action, reducer 개념을 사용한 단방향 데이터 흐름 - ex) redux, zustand
  4. proxy : 원본 객체에 대한 모든 접근을 감지하고 중간에서 가로채는 방식 - ex) mobX, Valtio
  5. server cache : 서버 요청 등과 같은 비동기 처리에 용이함 - ex) react-query(Tanstack Query), SWR

Recoil

리코일은 React를 만든 페이스북 팀에서 개발한 전역상태관리 라이브러리입니다.

기존의 React Context API에서는 데이터 변경 시 Provider 내부의 연관되지 않은 요소의 렌더링까지 함께 발생했어요. 따라서 이를 보완하기 위해, atom이라는 독립적인 store 단위를 분리하여 직접 접근할 수 있도록 만들었습니다.

리코일의 장단점

리코일은 문법이 react와 매우 유사하고, 사용법이 단순하여 Learning Curve가 낮다는 장점이 있습니다. 반복적인 코드 작성이 많고, 낯선 문법의 다른 라이브러리에 비해 큰 장점이라고 할 수 있죠.

규모가 작은 경우, 어떤 컴포넌트 위치에서도 상태에 간단하게 직접 접근할 수 있는 것이 편리한 장점입니다.

하지만 기능이 추가되고 관리하는 전역 상태 갯수가 많아지면, 어느 시점에서 어떤 컴포넌트의 상태가 변경되었는지 추적하고 예측하기 어려워져서 결국 단점이 될 수 있습니다.

atom과 selector

리코일 공식 홈페이지에 소개되어 있는 TO-DO List 예시 코드와 함께 기본 문법을 알아보겠습니다.

리코일에서는 크게 2가지 상태가 있습니다.

atoms(공유 상태)

const todoListState = atom({
	key: "todoListState",
	default: [],
});

atoms는 상태의 단위로, 업데이트(setter)와 구독(getter)이 가능해요! 특정 atom이 업데이트되면 이 아톰을 구독하고 있던 컴포넌트는 새로운 값을 반영하기 위해 자동으로 렌더링되죠. 그리고 아톰을 여러 컴포넌트에서 공유해서 사용할 수 있습니다.

하나의 아톰은 고유의 key 값으로 구분되며, recoil의 다양한 훅을 이용해서 사용할 수 있습니다. naming부터가 react의 useState 훅과 매우 유사하다는 것을 볼 수 있죠.

useRecoilValue

useRecoilValue

useSetRecoilState

selector(순수 함수)

const todoListStatsState = selector({
	key: "todoListStatsState",
	get: ({ get }) => {
		const todoList = get(todoListState);
		const totalNum = todoList.length;
		const totalCompletedNum = todoList.filter((item) => item.isComplete).length;

		return {
			totalNum,
			totalCompletedNum,
		};
	},
});

아톰은 상태를 의미하는 단위이고, 셀렉터는 이 상태를 참조하고 수정하는 순수 함수에서 나온 결과값(derived state, 파생된 상태)을 return 해줍니다.

아톰뿐만 아니라, 굳이 셀렉터라는 개념을 쓰는 이유는 다른 데이터에 의존하는 동적인 데이터를 만들 수 있기 때문입니다. 즉, store는 최소한으로 만들고, 이 아톰을 재사용하는 것이 코드양과 저장공간을 줄이는 효율적인 방법이 되겠죠?

셀렉터의 get 속성에는 ‘계산+결과값 반환’을 위한 콜백 함수을 전달하면 됩니다. 그리고 콜백함수 내에서 get 인자를 통해 다른 아톰 또는 셀렉터에 접근할 수 있고, 접근 시 종속 관계가 생성되죠! 이 덕분에 참조하는 아톰 또는 셀렉터가 업데이트되면 이 함수도 자동으로 다시 실행됩니다.

갑자기 Family?

const myAtomFamily = atomFamily({
  key: ‘MyAtom’,
  default: selectorFamily({
    key: 'MyAtom/Default',
    get: param => ({get}) => {
      return computeDefaultUsingParam(param);
    },
  }),
});

atom과 selector에는 매개변수가 없어요. 따라서 초기값이 정해진 로직에 따라 정해지게 되는데요. 하지만 atomFamily와 selectorFamily라는 개념을 이용하면, 전달받는 인수에 따라 동적으로 생성된 결과값을 리턴해줄 수 있습니다!


어떤 라이브러리를 써야할까?

처음부터 큰 전역상태관리 규모를 만들어야 할것이라면 리코일을 쓰면 안됩니다. 하지만 작은 규모로 빨리 만들어야 한다면 리코일을 선택하는 것은 좋은 선택입니다.

결국 처음부터 자신이 진행하려는 프로젝트의 규모 및 상황을 잘 고려해서 선택하는 것이 중요합니다.


참고자료

FECONF 2021 [B1] 상태관리, 이제 Recoil 하세요

FECONF 2022 [B5] 상태관리 이 전쟁을 끝내러 왔다

Recoil 공식 홈페이지

iOS 개발 - MVC 패턴과 UIKit의 ViewController

Web: React Flux 패턴

상태관리 라이브러리 비교: Redux vs Recoil vs Zustand vs Jotai

Recoil을 이용한 손쉬운 상태관리

npm trends

Props Drilling과 상태관리 라이브러리


INDEX

  1. 상태관리의 등장 배경
  2. 상태관리의 의미
  3. 전역상태관리의 등장
  4. 다양한 상태관리 라이브러리
  5. 주요 특징
  6. Recoil
  7. 리코일의 장단점
  8. atom과 selector
  9. atoms(공유 상태)
  10. selector(순수 함수)
  11. 갑자기 Family?
  12. 어떤 라이브러리를 써야할까?
  13. 참고자료

Written by @Hokyeong Choi

넥스트이노베이션의 기술, 문화, 뉴스, 행사 등 최신 소식 및 넥스트이노베이션이 겪은 다양한 경험을 공유합니다.