

☎️ 010-8915-5345
🏫 부산SW마이스터고(재학중)
경험을 통해 사용자와 소통하고 싶은 디자이너, 최정혁 입니다.

2024.12 ~ 2025.07 (지속적으로 버전 업데이트 중)
F/E 개발자로써 상태 관리 라이브러리를 직접 구현한다면 내부적인 동작 원리를 이해할 수 있다고 생각했습니다. Redux, Zustand등 라이브러리를 사용하면서 기존 라이브러리들이 범용성을 위해 포함하는 기능들이 환경에 따라 과한 스펙이라고 느껴지는 경우도 많았습니다.
따라서, 초경량 상태 관리 라이브러리 Statio는 기존의 상태관리 라이브러리보다 가벼운 경량 모듈을 장점으로 합니다. 개발자와 사용자 모두의 경험적 만족을 가장 우선으로 애플리케이션의 복잡한 흐름을 추적하고, 예상할 수 있는 데이터 흐름을 생성하고 유추할 수 있습니다. 범용 라이브러리보단, react의 반응성 시스템과 자연스럽게 연결되는 것을 목표로 했습니다.
<aside>
여러 상태를 연속으로 변경할 때 set을 개별 호출하면 각 호출마다 구독 중인 리스너가 즉시 실행되어 리렌더링이 N번 발생했습니다.
예를 들어 user, token, permission 3개의 상태를 로그인 처리 시 한 번에 갱신하면 컴포넌트가 3회 연속 리렌더링되었고,
상태가 완전히 반영되기 전인 중간 상태가 UI에 노출되는 문제도 확인되었습니다.
batch API를 도입해 여러 set 호출을 하나의 트랜잭션으로 묶었습니다. isBatching 플래그가 활성화된 동안 변경된 키를 batchedKeys에 적재만 하고, batch 블록이 종료되는 시점에 변경된 키들의 리스너를 일괄 호출하도록 설계했습니다.
이로써 N번의 리렌더링이 1번으로 줄었고, 중간 상태 노출 문제도 제거되었습니다.
</aside>
sequenceDiagram
participant C as 컴포넌트
participant GS as GlobalStore
participant BK as batchedKeys
participant L as 리스너
Note over C, L: Before — batch 미사용 시
C->>GS: set("user", data)
GS->>L: 리스너 즉시 호출
L->>C: 리렌더링 1회 (중간 상태)
C->>GS: set("token", token)
GS->>L: 리스너 즉시 호출
L->>C: 리렌더링 2회 (중간 상태)
C->>GS: set("permission", role)
GS->>L: 리스너 즉시 호출
L->>C: 리렌더링 3회 (최종 상태)
Note over C: 상태 3개 변경 → 리렌더링 3회<br/>중간 상태 UI 노출 발생
Note over C, L: After — batch 사용 시
C->>GS: batch() 시작 — isBatching = true
C->>GS: set("user", data)
GS->>BK: "user" 키 적재 (리스너 보류)
C->>GS: set("token", token)
GS->>BK: "token" 키 적재 (리스너 보류)
C->>GS: set("permission", role)
GS->>BK: "permission" 키 적재 (리스너 보류)
Note over GS: batch() 종료 — isBatching = false
GS->>BK: batchedKeys 순회
BK->>L: 변경된 키 리스너 일괄 호출
L->>C: 리렌더링 1회 (최종 상태)
Note over C: 상태 3개 변경 → 리렌더링 1회<br/>중간 상태 노출 제거
<aside>
GlobalStore의 리스너 관리에 자체 구현한 FastSet, FastHashMap을 도입했으나 오히려 성능이 저하되었습니다.
FastSet은 내부적으로 배열을 기반으로 구현되어 add(), has(), delete()모두 O(n) 순회가 발생했고, 구독자가 많아질수록
상태 변경 1회당 리스너 탐색 비용이 선형으로 증가했습니다. FastHashMap역시 V8 엔진이 히든 클래스(Hidden Class)와
인라인 캐싱(Inline Cache)으로 최적화한 네이티브 Map 의 성능에 미치지 못했으며, 커스텀 해시 충돌 처리 로직에서
잠재적 버그 가능성도 확인되었습니다.
FastSet → 네이티브 Set, FastHashMap → 네이티브 Map으로 교체했습니다. 네이티브 Set은 해시 기반으로
add(), has(), delete()가 평균 O(1)이며, Map 역시 V8이 객체 구조를 정적으로 분석해 접근을 최적화 했습니다.
자체 구현 코드를 제거함으로써 유지보수 복잡도와 버그 가능성도 함께 낮아졌습니다.
</aside>
sequenceDiagram
participant C as 컴포넌트
participant GS as GlobalStore
participant FS as FastSet (배열 기반)
participant NS as Native Set (해시 기반)
Note over C, FS: Before — FastSet 사용 시
C->>GS: set("count", 1)
GS->>FS: 리스너 탐색 요청
FS-->>FS: 배열 index 0 확인
FS-->>FS: 배열 index 1 확인
FS-->>FS: 배열 index 2 확인
FS-->>FS: ... 구독자 수만큼 O(n) 순회
FS-->>GS: 리스너 반환 (지연 발생)
GS->>C: 리스너 호출 → 리렌더링
Note over FS: 구독자 100개 시 100회 순회<br/>구독자 증가 → 지연 선형 증가
Note over C, NS: After — Native Set 교체 후
C->>GS: set("count", 1)
GS->>NS: 리스너 탐색 요청
NS-->>NS: 해시로 즉시 조회 O(1)
NS-->>GS: 리스너 반환 (지연 없음)
GS->>C: 리스너 호출 → 리렌더링
Note over NS: 구독자 100개여도 1회 조회<br/>V8 인라인 캐싱으로 추가 최적화
Note over C, NS: 커스텀 구현 제거 → 버그 가능성 감소 + 코드 복잡도 하락