React Atomic State Dispatcher 만들기
React로 웹을 작성하면서 느끼는 단점들이 몇가지가 있다. 그 중에서 느끼는 대표적인 단점으로 상태를 변경하게 되었을 때 얼마나 많은 렌더를 발생하게 하고 그것이 전체 자원을 얼마나 사용하는지 잘 모르게 한다는 점이다.
React Atomic 상태 관리 모듈 만들기
React로 프런트엔드를 개발 하다보면 느껴지는 불편한 것들이 몇가지가 있다. 그 중에서 한 가지로, 상태를 변경하게 되었을 때 얼마나 많은 컴포넌트들이 렌더를 발생하고, 그것이 전체 자원을 얼마나 사용하는지 파악이 쉽지 않다는 점이다.
하나의 상태 변경이 모든 컴포넌트의 리렌더를 일으킬 수도 있다는 것인데, 이는 개발자의 역량, 설계한 컴포넌트 구조에 따라 많은 차이를 보이게 한다.
그래서 리액트 기반 기술 스택을 선정할 때, 어떤 상태 관리 시스템을 이용할 지, 전역 상태 관리는 어떻게 할지에 대하여 깊은 설계 과정이 필요하다.
recoil, jotai 등은 대표적인 Atomic 상태 관리 라이브러리로 분류된다. Atomic 상태 관리 라이브러리들은 독립적인 상태를 정의하고, 그것을 참조하는 컴포넌트만 리렌더가 스케줄링 되도록 효율적인 리렌더 관리를 도와준다.
이 글은 이러한 Atomic 상태 관리 시스템을 기본적인 React Context API만으로 단순하게 구현해보는 간단한 글이다.
React Developer Tools는 어떤 컴포넌트가 리렌더가 발생하는지를 시각적으로 보여주는 크롬 extension이다. 이를 설치한다면 얼마나 많은 리렌더가 일어나는지 시각적인 표현, 프로파일링이 가능하다.
React의 상태와 리렌더
React는 상태를 정의하고, 컴포넌트의 상태 변화를 알리는 Dispatch를 통해 리렌더 실행이 예약된다. useState hook으로 상태를 정의하면 상태를 알 수 있는 Getter와 상태를 변경할 수 있는 Dispatch 함수를 얻게 된다.

Form Component Example
위 그림에서 setName 를 호출하여 다음 상태 값을 넘겨주면, 컴포넌트의 리렌더가 예약된다. 이렇게 상태를 변경하여 다음 리렌더를 호출하게 해주는 함수를 dispatcher 라고 하자.
이 dispatcher는 useReducer hook을 통해서도 얻을 수 있다. 특별한 상황에서 리렌더를 제어하고자 한다면, 이렇게 얻은 dispatcher 를 컨텍스트에 보관하고 필요 시 호출하여 상태 변경에 대한 액션을 세밀하게 제어할 수 있다.
Atomic 상태 관리를 위한 Context 설계
useState , useReducer 등을 통해 컴포넌트 리렌더를 일으킬 수 있는 dispatcher 가 있다는 것을 알아보았다.
그러면 어떻게 이를 관리하는 구조를 작성할 수 있을까?
먼저 AtomStore라는 Context를 설계하였다. 이 객체는 내부적으로 atom 오브젝트를 key로, 해당하는 인스턴스 정보를 value를 가지는 Map 오브젝트를 가지도록 구성하였다.

AtomStore
이것은 atom이 현재 Store에 등록되면 전역적으로 관리 해주는 객체이다. Store는 <AtomContext/>컴포넌트로 분리하여 관리할 수도 있으며, Atom은 Store와는 독립적인 관계로 순수하게 기본 값만을 정의하도록 단순하게 설계되었다. (자세한건 하단의 소스 코드 URL을 통해 참조하길 바란다.)
리액트 컴포넌트에서 사용하기위해 아래와 같은 3개의 hook을 작성하였다.
- useAtomValue: atom 값을 구독
- useSetAtom: atom 값을 변경하는 dispatcher
- useAtom: useAtomValue와 useSetAtom 2개를 호출하여 useState와 같은 사용성을 제공
useAtomValue의 구현
useReducer를 통해 현재 컴포넌트의 리렌더를 호출할 수 있는 dispatcher를 얻고, 이를 AtomStore에 등록하는 방식으로 구현되었다. 이렇게 구현한 방식은 컴포넌트가 제거되었을 때, AtomStore에서도 제거하여야 하므로 useEffect 를 통해 관리되도록 하였다. 여기서 사용한 reducer 함수는 매우 단순하다. 그저 state를 받고 바로 해당 state로 업데이트 해주는 내용이 전부이다.
function useAtomValue<T>(atom: Atom<T>) {
const { getAtomValue, addDispatch, removeDispatch } = useContext(atomContext);
const [state, dispatch] = useReducer(reducer, getAtomValue(atom));
useEffect(function registerAtomDispatch() {
addDispatch(atom, dispatch);
return () => removeDispatch(atom, dispatch);
}, []);
return state as T;
}
useSetAtom의 구현
매우 단순하다. 현재 속한 Context의 AtomStore에 등록된 atom 인스턴스를 가져와 dispatchList에 있는 모든 dispatch를 발생시키는 것으로 구현되었다.
function dispatchAtomState<T>(atom: Atom<T>) {
return function setAtomState(value: T) {
const instance = getAtomInstance(atom);
instance.value = value;
instance.dispatchList.forEach((dispatch) => dispatch(value));
};
}
function useSetAtom<T>(atom: Atom<T>) {
return useContext(atomContext).dispatchAtomState<T>(atom);
}
구현 결과
Atom으로 구현된 Form은 Atom을 참조하는 컴포넌트만 리렌더가 실행되는 것을 확인할 수 있다.

Atom Form
Atom을 사용하지 않고 일반 useState로 Form을 처리하는 방식은 Form이라는 큰 컴포넌트가 리렌더 됨을 알 수 있다.

- Demo: https://lee-gyu.github.io/react-atomic-state-example/
- Github: https://github.com/lee-gyu/react-atomic-state-example
Atom으로 처리하는 컴포넌트는 항상 효율적일까?
위 예제에서는 단순하게 useState로 구현한 방식이 자원을 적게 사용한다. 컴포넌트의 구조와 사용하는 hook에 따라 리렌더가 만드는 비용은 큰 차이를 보일 수 있다.
먼저, atom이라는 것을 저장하기 위한 추가적인 메모리가 필요하다는 것을 위 구현을 통해 보게 되었다. dispatcher를 Context에 저장하고, atom의 상태를 변경하면 해당 atom을 참조하는 모든 컴포넌트들의 리렌더를 발생시킨다.
Atom을 사용하지 않는 방식은 단일 컴포넌트로, 하나의 컴포넌트만 리렌더가 발생한다.
Atom을 사용하는 방식은 atom을 호출하는 여러 컴포넌트의 리렌더 발생을 호출하도록 구성이 되어있다. 이는 경우에 따라서는 약간의 오버헤드를 가지게 된다.
대부분의 경우에서 이러한 작은 오버헤드는 유의미한 영향이 있지 않을 것이다.
정리하며
애플리케이션의 컴포넌트 구조가 복잡한 경우에는 Atom 기반 기술로 상태를 관리하는 것이 리렌더를 효율적이게 제어할 수 있을 것이다.
중요한 것은 이러한 Atomic 상태 관리 없이도 적절한 메모이제이션이나 별도의 리렌더를 피하는 테크닉을 이용하여 무거운 리렌더 작업을 효율적이게 처리하는 기법이 있다.
Atomic 상태 관리 라이브러리를 막연히 사용하기 보다는, 근본적인 리액트 렌더 동작을 먼저 이해하고 사용을 생각하는 것은 어떨까 싶다.
메타데이터
- post_id
- ddaf207a3e9a
- slug
- react-atomic-state-dispatcher-만들기-ddaf207a3e9a
- url
- https://medium.com/@gyuc219/react-atomic-state-dispatcher-%EB%A7%8C%EB%93%A4%EA%B8%B0-ddaf207a3e9a
- canonical_url
- https://medium.com/@gyuc219/react-atomic-state-dispatcher-%EB%A7%8C%EB%93%A4%EA%B8%B0-ddaf207a3e9a
- author_url
- https://medium.com/@gyuc219
- status
- ok
- fetched_at
- 2026-06-14 11:28:49