지금 일하게 된 회사에서 상태관리를 할 때 Zustand를 쓴다는 것을 알게되었다. 사실 동료분이 "저희 회사는 Zustand 써서 상태관리 하고 있는데 혹시 뭔지 아시나요?" 라고 물어보셨을 때 진심 난생 처음 들어본다는 말 밖에 못했었다 😅
아직 본격적인 개발 업무가 주어지지 않았기 때문에 기본 개념부터 간단하게 정리하려고 한다!
리액트에서 상태관리를 하기 위해 사용하고 있는 Redux, Recoil, MobX, Jotai... 기타 등등의 상태관리 라이브러리들과 동일한 역할 수행하는 도구이다.
물론 각 라이브러리들만의 장단점이 있고, 프로젝트의 성격이나 규모에 따라 상황에 맞게 골라서 사용하면 된다고 한다. 어쨌든 본질은 복잡한 컴포넌트로 이뤄져있는 프로젝트에서 상태관리를 쉽게 하기 위함이고, 그 중에Zustand는 작성하는 코드의 양이 적고 가벼운 편으로 보인다.
Zustand는 Jotai를 개발한 회사에서 만들었다고 하고, React의 zombie children, React Concurrency, context loss에 대한 해결책을 제시하기 위해서 제작되었다고 한다.
concurrent mode라는 기능이 있는데, 리액트에서 렌더링되는 요소에서 일어나는 작업들을 "chunk"로 나눠서 더 수월하고 효과적인 방법으로 렌더링 관리를 할 수 있게 한다고 한다.E > D > C > B > A 형식으로 너무 깊숙하게 얽혀있다면 context에 접근이 제대로 되지 않을 수 있다는 의미다.기본적으로 Zustand는 설치 후 특정 파일에 스토어를 생성해서 관리하고 싶은 요소들을 넣어두고, 스토어와 컴포넌트를 바인딩해서 사용한다.
npm install zustand
yarn add zustand
Zustand는 store를 생성해서 관리할 상태들과, 그 상태들의 값을 변경할 수 있는 setState() 함수를 저장한다. 그리고 상태를 사용하고 싶은 컴포넌트엔 상태를 import하고, setState() 함수를 사용하고 싶다면 함수를 컴포넌트에 import하여 사용한다.
Zustand의 동작원리와 사용법은 더 무궁무진하지만, 일단 나는 여기서 기본적인 개념과 Flat update만 적어두려고 한다.
import { create } from 'zustand'
// 1. `create()` 함수를 import하여 스토어를 만들 수 있도록 한다
const useBearStore = create((set) => ({
// 2. 스토어의 이름을 지정하고 "set"을 매개변수로 받도록 지정한다.
bears: 0,
// 3. 관리하고 싶은 상태를 스토어에 저장하고,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
// 4. 특정 상태를 변경하는 함수를 생성한다.
// 4-1. 해당 함수는 setState() 함수로 상태를 변경하기 때문에 set을 스토어가 매개변수로 받는다.
removeAllBears: () => set({ bears: 0 }),
// 5. 관리할 상태는 1개 이상일 수 있고, 해당 상태를 변경하는 함수도 1개 이상일 수 있다.
}))
위의 예시를 보면 bears 라는 변수는 기본값이 0으로 되어있다.
그리고 해당 상태를 1씩 증가시키는 increasePopulation 함수와, 초기값으로 상태를 되돌리는 removeAllBears 함수가 있다. 이제 각각 함수를 사용해서 원하는 컴포넌트에서 상태를 변경할 수 있도록 import를 해야한다.
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} around here ...</h1>
}
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
스토어에 있는 값을 사용하기 위해선 컴포넌트에 변수/상수를 선언하고, 해당 변수/상수에 원하는 값을 store에서 꺼내와서 할당한다. 함수에 경우 이벤트를 발생하는 요소에 심어서 상태를 변경할 수 있다.
만약 스토어에서 1개 이상의 값을 가져오고싶다면, 간단하게 구조분해할당 문법을 사용해서 값을 손쉽게 가져올 수 있다.
const useStore = create((set) => ({
count: 0,
message: 'Hello, Zustand!',
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
updateMessage: (newMessage) => set({ message: newMessage }),
}))
function MyComponent() {
const { count, message, increment, decrement, updateMessage } = useStore()
return (
<div>
<p>Count: {count}</p>
<p>Message: {message}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={() => updateMessage('Zustand is awesome!')}>
Update Message
</button>
</div>
)
}
정말 기본적인 개념만 알아보았는데, 일단 보일러플레이트 코드가 굉장히 적어서 놀라울 정도였다.
state를 변경하는 법도 꽤나 직관적이라 러닝커브도 적어보이기 때문에 회사의 소스코드를 열심히 보면서 사용법과 응용방식을 익혀야겠다.
이전에 프로젝트를 진행할 땐 Redux만 사용해서 보일러플레이트 코드가 있다고해도 불편한점을 직관적으로 느끼지 못 했었는데, 이렇게 간결한 코드만으로 상태관리를 할 수 있는 라이브러리를 보니 "보일러플레이트 코드"에 대한 느낌이 어떤것인지 더 명확하게 알 수 있었다.
물론 프로젝트의 성격에 따라 라이브러리를 선택해야한다고는 하지만, 본질이 상태관리인만큼 점점 Redux를 사용하는 프로젝트가 줄어들지 않을까... 라는 생각이 든다.
Zustand는 꽤나 좋은 라이브러리인 것 같다!
출처: