[TIL 2023.04.12] Zustand 기본 개념

김헤일리·2023년 4월 12일
0

TIL

목록 보기
45/46

지금 일하게 된 회사에서 상태관리를 할 때 Zustand를 쓴다는 것을 알게되었다. 사실 동료분이 "저희 회사는 Zustand 써서 상태관리 하고 있는데 혹시 뭔지 아시나요?" 라고 물어보셨을 때 진심 난생 처음 들어본다는 말 밖에 못했었다 😅

아직 본격적인 개발 업무가 주어지지 않았기 때문에 기본 개념부터 간단하게 정리하려고 한다!


1. Zustand 란?

리액트에서 상태관리를 하기 위해 사용하고 있는 Redux, Recoil, MobX, Jotai... 기타 등등의 상태관리 라이브러리들과 동일한 역할 수행하는 도구이다.

물론 각 라이브러리들만의 장단점이 있고, 프로젝트의 성격이나 규모에 따라 상황에 맞게 골라서 사용하면 된다고 한다. 어쨌든 본질은 복잡한 컴포넌트로 이뤄져있는 프로젝트에서 상태관리를 쉽게 하기 위함이고, 그 중에Zustand는 작성하는 코드의 양이 적고 가벼운 편으로 보인다.

Zustand는 Jotai를 개발한 회사에서 만들었다고 하고, React의 zombie children, React Concurrency, context loss에 대한 해결책을 제시하기 위해서 제작되었다고 한다.

1. zombie children:

  • 리액트에서 일어나는 문제 중 하나로, 하위 컴포넌트인 "A" 에서 실행하던 어떠한 일이 끝나기 전에 상위 컴포넌트 "B" 가 unmount 됐을 때 생기는 일이라고 한다.
  • 예를 들어, A에서 시간이 좀 걸리는 비동기 함수를 처리하고 있을 때 갑자기 B가 unmount 되어버리면 A는 "zombie children"이 된다. A는 아직 DOM에 존재하는데 A의 연결고리인 B가 사라진 셈이다. 이 경우, A가 작업을 끝낸 후 화면에서 사라진다고해도 메모리를 지속적으로 잡아먹는다고 한다.
  • zombie children을 방지하기 위해선 부모 컴포넌트가 사라지기 전에 자식 컴포넌트가 확실하게 unmount될 수 있도록 라이프 사이클을 관리해야한다고 한다.

2. React Concurrency

  • 리액트엔 concurrent mode라는 기능이 있는데, 리액트에서 렌더링되는 요소에서 일어나는 작업들을 "chunk"로 나눠서 더 수월하고 효과적인 방법으로 렌더링 관리를 할 수 있게 한다고 한다.
  • 동시성 모드에서 각 "chunk"들은 사용자의 인터랙션 이벤트와 데이터 업데이트 등으로 나누어져서 우선순위에 따라 먼저 업데이트된다.
  • 동시성 모드는 동기적으로 UI가 렌더링되는 기존의 방식과 다르게 동시적으로 업데이트되기 때문에 렌더링 순서를 예측하기가 힘들다. 이때 잘못하면 깜빡임 현상 같은 것이 생겨나 사용성이 좋지 않을 수 있다.
  • 상대적으로 새로운 기능이기 때문에 외부 라이브러리들과의 호환성 문제가 생길 수도 있고, 렌더링의 상태가 최신이라는 보장도 할 수 없어 프로젝트를 구성할 때 개발자가 예측하기 힘들다는 문제가 있다.

3. context loss

  • 컴포넌트 트리가 너무 복잡할 때 특정 컴포넌트는 context data에 접근하기 힘들 수 있다.
  • context에 저장되어있는 사용자의 정보를 컴포넌트 A가 접근하려고 할 때 A가 위치한 구조가 E > D > C > B > A 형식으로 너무 깊숙하게 얽혀있다면 context에 접근이 제대로 되지 않을 수 있다는 의미다.
  • 컴포넌트가 사용해야하는 정보에 접근할 수 없다면 문제가 생길 여지가 매우 많기 때문에 컴포넌트가 context에 제대로 접근하고 있는지 확인을 해야한다.

기본적으로 Zustand는 설치 후 특정 파일에 스토어를 생성해서 관리하고 싶은 요소들을 넣어두고, 스토어와 컴포넌트를 바인딩해서 사용한다.

설치:

npm install zustand

yarn add zustand

2. Zustand 사용법

Zustand는 store를 생성해서 관리할 상태들과, 그 상태들의 값을 변경할 수 있는 setState() 함수를 저장한다. 그리고 상태를 사용하고 싶은 컴포넌트엔 상태를 import하고, setState() 함수를 사용하고 싶다면 함수를 컴포넌트에 import하여 사용한다.

Zustand의 동작원리와 사용법은 더 무궁무진하지만, 일단 나는 여기서 기본적인 개념과 Flat update만 적어두려고 한다.

❗️ store 생성하기

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를 해야한다.


❗️ state 업데이트하기

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는 꽤나 좋은 라이브러리인 것 같다!



출처:

profile
공부하느라 녹는 중... 밖에 안 나가서 버섯 피는 중... 🍄

0개의 댓글