Reatom: 당신과 함께 성장하는 State Management

발행: (2025년 12월 8일 오후 09:47 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

Fragmentation 문제

현대 프론트엔드 개발에는 익숙한 패턴이 있습니다:

  • 간단한 useState 훅으로 시작
  • 공유 상태가 필요해? Context 추가
  • Context가 너무 많이 리렌더링? 상태 관리 라이브러리 추가
  • 상태 관리가 비동기를 잘 다루지 못해? 데이터‑패칭 라이브러리 추가
  • 폼이 필요해? 또 다른 라이브러리
  • 라우트에 데이터 로딩이 필요해? 로더가 있는 라우터
  • 영속성? 또 다른 라이브러리
  • 로깅과 분석? 또 시작…

각 도구마다 고유한 사고 모델, API 특이점, 번들 비용이 있습니다. 스택이 커지고 복잡도가 늘어나며, 새 팀원이 전체를 이해하는 데 몇 주가 걸립니다. 여러 라이브러리 간 버전 관리와 호환성 유지가 악몽이 됩니다.

Reatom은 다른 접근법을 제시합니다. 가장 간단한 카운터부터 가장 복잡한 엔터프라이즈 데이터 흐름까지 모든 고민을 처리할 수 있는 하나의 일관된 시스템입니다. 필요한 것만 사용하고, 기존에 가지고 있는 것과 통합하세요.

빠른 개요는 사이트를 확인해 보세요:

Zen

Reatom 설계는 네 가지 원칙에 기반합니다:

  • 프리미티브가 프레임워크보다 우수 – 몇 개의 강력한 빌딩 블록이 복잡한 프레임워크보다 낫다.
  • 구성보다 조합 – 거대한 모놀리스를 설정하기보다 간단한 조각들을 쌓아라.
  • 명시적인 구체성, 암시적인 일반성 – 중요한 것은 명확히 하고, 보일러플레이트는 숨겨라.
  • 복잡성을 감수할 가치가 있는 호환성 – 당신의 스택과 함께 작동하고, 방해하지 않는다.

이것은 마케팅 문구가 아니라, 6년간의 프로덕션 사용과 지속적인 개선을 거쳐 나온 결과이며, 2019년 12월 첫 LTS 릴리스를 시작으로 형성되었습니다.

간단히 시작하기

시그널에 익숙하다면 Reatom은 자연스럽게 느껴질 것입니다:

import { atom, computed } from '@reatom/core'

const counter = atom(0)
const isEven = computed(() => counter() % 2 === 0)

// Read
console.log(counter()) // 0
console.log(isEven()) // true

// Write
counter.set(5)
counter.set(prev => prev + 1)

프로바이더도, 보일러플레이트도, 번거로운 절차도 없습니다. 핵심은 gzip 압축 시 3 KB 미만으로, 일부 “경량” 대안보다도 작습니다.

여기서 멈출 수도 있지만, 흥미로운 부분은 다음과 같습니다…

무한히 성장하기

카운터를 구동하는 동일한 프리미티브가 엔터프라이즈 수준의 복잡성도 처리합니다:

import {
  atom,
  computed,
  withAsyncData,
  withSearchParams,
} from '@reatom/core'

// Sync with URL search params automatically
const search = atom('', 'search').extend(withSearchParams('search'))
const page = atom(1, 'page').extend(withSearchParams('page'))

// Async computed with automatic cancellation
const searchResults = computed(async () => {
  await wrap(sleep(300)) // debounce

  const response = await wrap(fetch(`/api/search?q=${search()}&page=${page()}`))
  return await wrap(response.json())
}, 'searchResults').extend(withAsyncData({ initState: [] }))

// Usage
searchResults.ready() // loading state
searchResults.data()   // the results
searchResults.error()  // any errors

여기서 일어나는 일

  • searchpage atom이 URL 파라미터와 자동으로 동기화됩니다.
  • 의존성이 변할 때 그리고 누군가가 구독할 때만(지연) computed가 다시 실행됩니다.
  • 사용자가 API 응답보다 빠르게 입력하면 이전 요청이 자동으로 취소되어 레이스 컨디션과 메모리 누수를 방지합니다.

이는 카운터 예제에서 사용한 동일한 atom에 확장을 입힌 것입니다.

확장 시스템

단일 프레임워크 대신, Reatom은 깔끔하게 쌓을 수 있는 조합 가능한 확장을 제공합니다:

const theme = atom('dark').extend(
  // Persist to localStorage
  withLocalStorage('theme')
)

const list = atom([]).extend(
  // Memoize equal changes
  withMemo()
)

const unreadCount = atom(0).extend(
  // React to state changes
  withChangeHook(count => {
    document.title = count > 0 ? `(${count}) My App` : 'My App'
  }),
  // Sync across tabs
  withBroadcastChannel('notificationsCounter')
)

각 확장은 하나의 일을 잘 수행합니다. 필요한 것만 정확히 조합하세요.

상태를 넘어: 완전한 솔루션

타입‑안전한 폼 관리와 일급 지원:

  • 필드‑레벨 및 폼‑레벨 검증 (동기·비동기)
  • 표준 스키마 검증기와 통합 (Zod, Valibot 등)
  • 추가·제거·재정렬이 가능한 동적 필드 배열
  • 포커스 추적, 더티 감지, 에러 관리
  • 문자열 경로가 아닌 객체와 atom 사용
  • 극도로 최적화되고 높은 성능

라우팅

타입‑안전 라우팅과 자동 라이프사이클 관리:

  • 파라미터 검증 및 변환
  • 네비게이션 시 자동 취소되는 데이터 로딩
  • 공유 레이아웃을 갖는 중첩 라우트
  • 검색 파라미터 처리
  • 이소모픽 크로스‑프레임워크 지원

팩토리 패턴 – 라우트 로더에서 생성된 상태는 네비게이션 시 자동으로 가비지 컬렉션되어 “전역 상태 정리” 문제를 해결합니다.

영속성

다양한 내장 스토리지 어댑터와 고급 기능:

  • localStorage, sessionStorage, BroadcastChannel, IndexedDB, 쿠키 및 쿠키 스토어
  • 표준 스키마 검증기와 통합 (Zod, Valibot 등)
  • 데이터 포맷 변경을 위한 버전 마이그레이션
  • TTL(수명) 지원
  • 사용할 수 없을 때 메모리 스토리지로 우아하게 폴백

깊이 있는 기술적 장점

원인 추적

Reatom은 TC39 AsyncContext를 에뮬레이트하며 다음과 같은 이점을 얻습니다:

  • 동시 비동기 체인의 자동 취소(디바운스와 유사)
  • 일부 IoC/DI 가능성
  • 비동기 트랜잭션
  • 프로세스 추적 및 로깅

마지막 기능은 복잡한 비동기 흐름에서 게임 체인저입니다. 동시 작업의 원인을 쉽게 파악해 디버깅 시간을 몇 시간에서 몇 분으로 단축할 수 있습니다.

성능

Reatom은 그 기능 집합에도 불구하고 최상위 수준의 성능을 제공합니다. 애플리케이션이 복잡할수록 대안보다 더 빠르게 동작합니다. 복잡한 계산에 대한 벤치마크는 Reatom이 중간 규모 의존성 그래프에서 MobX보다 뛰어난 성능을 보임을 보여줍니다. 이는 Reatom이 불변 데이터 구조를 사용하고 별도 비동기 컨텍스트에서 동작함에도 불구하고, 일반적으로 오버헤드가 발생하는 부분을 최소화했기 때문입니다.

명시적 반응성, 프록시 없음

프록시 기반 반응성은 간단한 경우에 편리하지만 대규모 코드베이스에서는 추적이 어려워집니다. Reatom에서는 어떤 속성이든 마우스를 올리면 타입 힌트를 확인할 수 있어 언제든 반응성이 명확합니다.

Reatom은 atom화를 장려합니다 — 백엔드 DTO를 애플리케이션 모델로 변환하면서 가변 속성을 atom으로 바꾸는 방식입니다:

// Backend DTO
type UserDto = { id: string; name: string }

// Application model with explicit reactivity
type User = { id: string; name: Atom }

const userModel = { id: dto.id, name: atom(dto.name) }

// Usage
userModel.id          // static value — no reactivity
userModel.name()      // reactive — tracks changes

이 패턴은 미세한 제어를 가능하게 합니다: 반응성이 필요한 요소를 정확히 정의하고, 불필요한 옵저버 생성을 방지하며, 반응 그래프를 명시적으로 유지합니다.

Back to Blog

관련 글

더 보기 »

전체 Redux 내부

Redux 내부 흐름 다이어그램 텍스트 ┌─────────────────────────────┐ │ Your Component │ │ dispatch action │ └──────────────┬──────────────...