React 애플리케이션에서 상태 관리는 매우 중요한 주제입니다. 특히 서비스 규모가 커질수록 상태를 어떻게 관리하느냐에 따라 유지보수성과 성능이 크게 좌우됩니다. 이번 글에서는 Zustand를 이용해 React 애플리케이션의 상태를 효율적으로 관리하는 방법을 알아보겠습니다.
우선 상태란 무엇일까요? 리액트에서 상태란 컴포넌트 내부의 데이터를 의미합니다. 상태는 시간에 따라, 또는 사용자의 상호작용에 의해 변경됩니다. 이런 변화 과정에서 의도한 대로 상호작용에 반응하도록 관리하는 것을 상태 관리라고 합니다. 상태관리는 컴포넌트 내부의 데이터 변화를 의미하는 지역 상태관리와 컴포넌트 간의 상태를 공유하는 전역 상태 관리로 나뉩니다.
Prop Drilling 문제
주목하셔야 하는 부분은 전역 상태관리입니다. 로그인 여부, 사용자 인증정보 참조, 테마 설정, 알림 기능 같이 여러 컴포넌트간의 데이터를 주고받는 전역 상태의 경우에는 가상 돔(Virtual DOM)을 활용하는 리액트의 특성상 상위 컴포넌트에서 하위 컴포넌트로 props(properties)를 통해 데이터가 전달됩니다. 문제는 컴포넌트 계층이 깊어질수록 데이터 전달 과정이 복잡해진다는 점입니다. 중간에 위치한 컴포넌트가 많아질수록, 직접적으로 데이터를 사용하지 않는 컴포넌트에서도 불필요하게 props를 처리해야 하는 상황이 발생합니다. 이를 Prop Drilling 문제라고 합니다. Prop Drilling이 발생하면 가독성이 저하되고, 유지보수가 어려워집니다.
리액트 생태계에서는 이러한 Prop Drilling 문제를 해결하기 위해 다양한 상태 관리 라이브러리들이 등장했습니다. Redux, MobX, Recoil 등이 대표적이며, 이들은 전역 저장소(store)를 통해 상태를 중앙 집중적으로 관리함으로써 컴포넌트 간의 직접적인 props 전달을 줄일 수 있게 해줍니다. 하지만 이러한 라이브러리들은 때로는 과도한 보일러플레이트 코드를 요구하거나, 러닝 커브가 높다는 단점이 있었습니다. 이러한 배경에서 더 간단하고 직관적인 상태 관리를 위해 Zustand가 등장하게 되었습니다.
Zustand 란?
A small, fast, and scalable bearbones state management solution. Zustand has a comfy API based on hooks. It isn’t boilerplatey or opinionated, but has enough convention to be explicit and flux-like. Don’t disregard it because it’s cute, it has claws! Lots of time was spent to deal with common pitfalls, like the dreaded zombie child problem, React concurrency, and context loss between mixed renderers. It may be the one state manager in the React space that gets all of these right.
출처: https://zustand.docs.pmnd.rs/getting-started/introduction
Zustand는 React 애플리케이션에서 상태 관리를 간단하고 효율적으로 구현할 수 있는 경량화 상태 관리 라이브러리입니다. 독일어로 “상태”를 의미하는 이름을 가진 Zustand는, 최소한의 설정으로 강력한 상태 관리 기능을 제공하며, React의 상태 관리 생태계에서 점점 더 주목받고 있습니다.
주요 특징은 다음과 같습니다.
- 간결함: Zustand는 보일러플레이트 코드 없이 간단히 상태를 정의하고 사용할 수 있습니다. useState 처럼 직관적인 API를 제공해 상태 관리에 대한 학습 곡선을 최소화합니다.
- React 독립적: React 컴포넌트에 종속되지 않으며, Vanilla JavaScript에서도 사용할 수 있습니다. 이는 Zustand가 다른 UI 프레임워크와도 호환 가능하다는 것을 의미합니다.
- 최적화: Zustand는 필요한 상태만 구독하여 리렌더링을 최소화합니다. 덕분에 대규모 애플리케이션에서도 높은 성능을 유지할 수 있습니다.
- 미들웨어 및 확장성: 로컬 스토리지에 상태를 유지하거나, 상태 업데이트 로직을 커스터마이징하는 미들웨어를 간단히 추가할 수 있습니다.
Zustand 사용방법
Zustand는 create 함수를 사용하여 상태를 정의하고, React 컴포넌트에서 이 상태를 useStore와 같은 방식으로 접근합니다. 공식문서의 예제 코드를 소개해 드리겠습니다.
Zustand 설치
먼저 Zustand를 프로젝트에 설치합니다.
npm install zustand
- 원하는 패키지 매니저를 사용하실 수 있습니다.
스토어 생성
Zustand에서 상태 관리 스토어는 훅(hook) 형태로 정의됩니다. 스토어는 기본적으로 JavaScript 객체이며, 상태 값(primitive, 객체, 함수 등)을 자유롭게 포함할 수 있습니다. 상태 업데이트는 set 함수를 사용해 수행됩니다.
import { create } from 'zustand';
const useStore = create((set) => ({
bears: 0, // 상태 값
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), // 상태 업데이트 함수
removeAllBears: () => set({ bears: 0 }), // 상태 초기화 함수
updateBears: (newBears) => set({ bears: newBears }), // 새로운 값으로 상태 업데이트
}));
컴포넌트에서 상태 사용
Zustand로 정의된 스토어는 컴포넌트 내에서 사용자 정의 훅으로 호출할 수 있습니다.
상태 읽기 및 렌더링
function BearCounter() {
const bears = useStore((state) => state.bears); // 상태 읽기
return <h1>{bears} around here...</h1>; // 상태 렌더링
}
상태 업데이트
상태 변경이 일어나면 해당 상태를 구독 중인 컴포넌트만 리렌더링됩니다.
function Controls() {
const increasePopulation = useStore((state) => state.increasePopulation); // 상태 업데이트 함수 가져오기
return <button onClick={increasePopulation}>one up</button>;
}
기타 라이브러리 대비 Zustand의 장단점
추가적으로 전역 상태를 관리할 때는 Redux, Recoil, Context API와 같은 라이브러리 또한 많이 사용됩니다. 이러한 라이브러리들은 상태를 전역적으로 공유하기 위해 Provider 컴포넌트를 컴포넌트 트리의 상위에 배치하고, 그 안의 하위 컴포넌트에서 상태를 소비(consumption)합니다. Provider가 없으면 상태를 전달할 수 없는 제한이 있습니다. 따라서 프로젝트 초기 설정 시나 복잡한 상태 관리 구조에서는 추가적인 구성 작업이 필요합니다.
반면 Zustand는 provider가 없어도 create 함수로 정의된 상태 스토어를 애플리케이션 어디서나 사용할 수 있다는 장점이 있습니다. 또한 Zustand는 상태 변경 시 해당 상태를 구독 중인 컴포넌트만 리렌더링합니다. 이러한 동작은 Redux와 Recoil에서도 가능하지만, 각각의 설정 복잡도와 비교했을 때 Zustand는 더 직관적이고 간결한 API를 제공합니다. 이를 통해 불필요한 리렌더링을 방지하며, 컴포넌트 트리가 깊거나 상태가 자주 변경되는 대규모 애플리케이션에서도 뛰어난 성능을 발휘할 수 있습니다. 이러한 설계는 애플리케이션의 데이터 흐름을 간결하고 효율적으로 만들어 줍니다.
Zustand는 간결한 API와 뛰어난 성능 최적화로 React 애플리케이션의 상태 관리를 훨씬 간단하고 효율적으로 만들어줍니다. Redux와 Recoil에 비해 설정이 간단하며, 불필요한 리렌더링을 방지하여 대규모 프로젝트에서도 높은 성능을 유지할 수 있습니다. 또한, React에 종속되지 않아 다양한 환경에서 활용 가능하다는 점도 큰 장점입니다. 간단하고 강력한 상태 관리 도구를 찾고 있다면 Zustand를 적극 추천합니다.
Ref)
https://zustand.docs.pmnd.rs/getting-started/introduction