alpaka206

Recoil로 간편한 상태 관리하기

date
Sep 13, 2024
slug
about-recoil
author
status
Public
tags
React
COMATCHING
summary
Recoil로 간편한 상태 관리하기
type
Post
thumbnail
category
💻 Frontend
updatedAt
Jan 4, 2026 06:54 AM
프로젝트를 진행하며 Recoil에 대해 깊이 공부하게 되었습니다. 그동안 atom만 사용하여 전역 상태 관리의 개념을 적용했지만, 이번 글에서는 selector 등 다양한 Recoil의 개념을 살펴보겠습니다.

왜 Recoil인가?

React는 단방향 데이터 흐름을 따릅니다. 부모 컴포넌트에서 자식 컴포넌트로만 props를 전달할 수 있으며, 자식 컴포넌트에서 부모의 상태를 직접 변경하는 것은 불가능합니다. 하지만 실제 프로젝트에서는 자식 컴포넌트가 부모의 값을 바꿔야 할 때가 많습니다. 이럴 때 사용할 수 있는 방법은 다음과 같습니다:
  1. 콜백 함수: 부모 컴포넌트에서 자식에게 상태를 변경할 수 있는 함수를 props로 전달합니다. 자식 컴포넌트는 이 함수를 호출함으로써 부모의 상태를 업데이트할 수 있습니다.
  1. 상태 끌어올리기: 공통의 부모 컴포넌트에서 상태를 관리하고, 필요한 자식 컴포넌트에 해당 상태와 변경 함수를 props로 전달하여 상태를 공유합니다.
  1. Context API: 전역적으로 데이터를 관리할 수 있는 Context를 생성하여, 자식 컴포넌트가 이 Context를 구독하고 필요한 데이터에 접근하도록 합니다. 이를 통해 컴포넌트 간의 깊은 계층에서도 쉽게 데이터를 전달할 수 있습니다.
  1. 상태 관리 라이브러리: Redux, Recoil과 같은 상태 관리 라이브러리를 사용하여 애플리케이션의 상태를 효율적으로 관리합니다. 이러한 라이브러리는 복잡한 상태 흐름을 단순화하고, 여러 컴포넌트 간의 상태 공유를 용이하게 합니다.
  1. 부모의 setState 함수를 props로 전달: 부모 컴포넌트의 setState 함수를 자식 컴포넌트에 props로 전달하여, 자식에서 이 함수를 호출함으로써 부모의 상태를 직접 수정할 수 있도록 합니다.
여기서 4번에 대해 좀 더 구체적으로 설명하겠습니다. 왜 Redux 대신 Recoil을 선택했는지 살펴보겠습니다.

Redux가 아닌 Recoil

Flux 아키텍처는 Redux의 기반입니다. Redux는 널리 사용되는 상태 관리 도구이지만, 액션, 리듀서, 셀렉터, 스토어를 초기 설정하는 데 많은 코드가 필요하고, React에 최적화되어 있지 않아 사용이 번거롭습니다.
반면, Recoil은 React를 위해 설계된 상태 관리 라이브러리입니다. Hooks를 사용한 경험이 있다면 쉽게 적응할 수 있습니다.

Atom

Atom은 상태의 단위입니다. atom을 정의하는 방법은 다음과 같습니다.
import { atom } from 'recoil'; const userState = atom({ key: 'userState', // 고유 ID default: '', // 기본값 });
atom이 업데이트되면 이를 구독하고 있는 모든 컴포넌트가 리렌더링됩니다. 각 atom은 여러 컴포넌트에서 공유될 수 있습니다.

Selector

Selector는 Recoil에서 파생된 상태를 나타내며, 기존 상태를 가공하여 새로운 값을 생성하는 데 사용됩니다. 이를 통해 여러 atom의 값을 조합하거나 변형하여 보다 복잡한 상태를 간편하게 관리할 수 있습니다. Selector는 일반적으로 상태를 읽기 전용으로 사용하지만, 특정 상황에서는 상태를 수정하는 데에도 사용할 수 있습니다.

Selector의 기본 사용법

Selector를 정의할 때는 get 함수를 사용하여 기존 atom의 값을 가져옵니다. 아래 예시는 사용자의 포인트 상태를 가져오는 Selector의 정의입니다.
import { selector } from 'recoil'; import { userState } from './atoms'; // atom을 import const userPointState = selector({ key: 'userPointState', // 고유 ID get: ({ get }) => { const point = get(userState); // userState atom의 값을 가져옴 return point; // userState의 값을 그대로 반환 }, });
위의 예제에서 userPointStateuserState atom의 값을 가져와 그대로 반환하는 Selector입니다. 이처럼 Selector는 기본적으로 기존 상태를 가공하여 새로운 값을 만들 때 유용합니다.

Selector에서 set 메서드 사용하기

Selector는 주로 읽기 전용으로 사용되지만, set 메서드를 정의하면 해당 Selector를 통해 다른 atom의 값을 수정할 수도 있습니다. set 메서드는 상태를 업데이트할 때 사용되며, 아래 예제에서 확인할 수 있습니다.
주의해야할 점은 selector는 순수함수여야하고 read-only한 객체로서 return값만 가질수 있고 set할수는 없습니다.
import { atom, selector } from 'recoil'; const userState = atom({ key: 'userState', default: { points: 0 }, }); const userPointState = selector({ key: 'userPointState', get: ({ get }) => { const user = get(userState); return user.points; // userState의 points 값을 반환 }, set: ({ set }, newValue) => { set(userState, (prevUser) => ({ ...prevUser, points: newValue, // points 값을 newValue로 업데이트 })); }, });
위의 예제에서 userPointState Selector는 set 메서드를 통해 userState atom의 points 값을 수정할 수 있는 기능을 추가했습니다. 이 메서드는 set 함수를 통해 atom의 상태를 업데이트하며, 기존 상태를 기반으로 새로운 값을 설정할 수 있습니다.

Selector의 사용 예시

Selector는 컴포넌트에서 쉽게 사용할 수 있습니다. 예를 들어, 아래와 같이 userPointState Selector를 사용하여 컴포넌트에서 포인트 값을 읽거나 수정할 수 있습니다.
import React from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { userPointState } from './selectors'; // selector import const UserProfile = () => { const points = useRecoilValue(userPointState); // Selector를 통해 points 값 읽기 const [setPoints] = useRecoilState(userPointState); // Selector의 set 사용 const increasePoints = () => { setPoints(points + 10); // 포인트 증가 }; return ( <div> <h1>User Points: {points}</h1> <button onClick={increasePoints}>Increase Points</button> </div> ); }; export default UserProfile;
 

비동기 상태 처리

Selector에서 비동기 처리를 할 때는 주의가 필요합니다. React의 Suspense를 활용하여 로딩 UI를 보여줄 수 있습니다.
import React, { Suspense } from 'react'; import { Mainpage } from '../components'; const App = () => { return ( <RootRecoil> <Suspense fallback={<div>Loading...</div>}> <Mainpage /> </Suspense> </RootRecoil> ); } export default App;

Loadable을 활용한 비동기 상태 관리

Recoil에서는 Loadable을 사용하여 atom이나 selector의 현재 상태를 확인할 수 있습니다.
import { useRecoilValueLoadable } from 'recoil'; const userLoadable = useRecoilValueLoadable(getUserSelector);
Loadable은 statecontents라는 프로퍼티를 가지며, 쉽게 접근할 수 있습니다.

Selector를 통한 성능 개선 - 캐싱

Selector의 캐싱 기능은 Recoil의 중요한 특징 중 하나로, 성능을 개선하고 불필요한 계산 및 API 호출을 줄이는 데 매우 유용합니다. Selector는 내부적으로 값의 변화를 추적하며, 동일한 값이 다시 요청되었을 때 이전에 계산된 결과를 재사용합니다. 이로 인해 동일한 계산이나 API 요청을 여러 번 수행할 필요가 없어집니다.

Selector 캐싱의 작동 방식

  1. 상태 추적: Selector는 사용된 atom의 값을 추적합니다. Atom의 값이 변경되면, 이를 구독하고 있는 Selector는 자동으로 리렌더링됩니다.
  1. 값 저장: Selector가 처음 호출되면 해당 값을 계산하고 캐시에 저장합니다. 이후 같은 값이 필요할 경우, Selector는 계산된 결과를 재사용합니다.
  1. 의존성 변경 시 업데이트: Selector가 의존하는 atom이나 다른 Selector의 값이 변경되면, Selector는 다시 계산되고 새로운 값을 반환합니다. 이 과정에서 캐시된 값은 무효화되고 새로운 값이 저장됩니다.
import { atom, selector } from 'recoil'; import axios from 'axios'; // Atom: 사용자 ID를 저장하는 상태 const userIdState = atom({ key: 'userIdState', default: '', }); // Selector: API를 호출하여 사용자 정보를 가져오는 Selector const userInfoSelector = selector({ key: 'userInfoSelector', get: async ({ get }) => { const userId = get(userIdState); if (!userId) return null; // userId가 없으면 null 반환 const response = await axios.get(`https://api.github.com/users/${userId}`); return response.data; // 사용자 정보 반환 }, });

동적인 URL에 대한 API 호출 - SelectorFamily

파라미터를 받아서 사용하는 selectorFamily 를 통해 동적인 URL을 처리할 수 있습니다.
import { selectorFamily } from 'recoil'; import axios from 'axios'; export const githubRepo = selectorFamily({ key: "github/get", get: (githubId) => async () => { if (!githubId) return ""; const { data } = await axios.get(`https://api.github.com/repos/${githubId}`); return data; }, });
selectorFamily를 사용하여 동적으로 GitHub API를 호출할 수 있습니다.
import { useRecoilValue } from 'recoil'; import { githubRepo } from '../../state'; const Github = () => { const githubId = 'juno7803'; const githubRepos = useRecoilValue(githubRepo(githubId)); return ( <div>Repos: {githubRepos}</div> ); } export default Github;

마무리 정리

Recoil은 React 애플리케이션의 상태 관리를 위한 매력적인 도구로, 특히 Hook을 이미 사용해본 개발자들에게는 매우 낮은 러닝 커브로 접근할 수 있는 큰 장점을 제공합니다. Redux와 비교했을 때, Recoil은 상태 관리의 복잡성을 줄이고, 많은 기능이 기본적으로 Hook 형태로 구현되어 있어 사용이 간편합니다.
그럼에도 불구하고 Recoil은 출시된 지 얼마 되지 않아 상대적으로 작은 생태계를 갖고 있으며, Atom과 Selector에 대한 기본적인 예제 외에는 깊이 있는 문서나 자료가 부족한 상황입니다. 그러나 Recoil이 앞으로 더 발전하고 확장됨에 따라, 다양한 상태 관리 기능과 패턴이 제시될 것으로 기대됩니다. 이러한 발전이 이루어지면, Recoil은 더욱 많은 개발자들에게 사랑받는 상태 관리 도구가 될 것이라 믿습니다.