티스토리 뷰

Programming/React

상태관리와 Zustand

s00oo 2024. 3. 19. 21:50

상태

- 어떠한 의미를 지닌 값

- 애플리케이션의 시나리오에 따라 지속적으로 변경될 수 있는 값

- 웹 애플리케이션을 구성하는 변경될 수 있는 데이터

 

 웹에서의 상태

✧ UI: 상호 작용이 가능한 모든 요소의 현재 값은 상태이다.

예) 다크/라이트 모드, 각종 input, 알림창의 노출 여부 등

 

✧ URL: 브라우저에서 관리되고 있는 상태값. 상태는 사용자의 라우팅에 따라 변경됨.

예) https://www.airbnb.co.kr/rooms/2024?adults=2  ➔ 상태: roomId=2024, adults=2

 

✧ 폼(form): 로딩 중인지(loading), 현재 제출됐는지(submit), 접근이 불가능한지(disabled), 값이 유효한지(validation) 등 모두가 상태로 관리된다.

 

✧ 서버에서 가져온 값: 클라이언트에서 서버로 요청을 통해 가져온 값. 

예) API 요청

 

 상태 관리

상태(변화하는 데이터)에 맞춰 적절하게 UX와 UI를 설계하고 구현하는 것

 

 상태 관리의 필요성

1. 상태(데이터)가 바뀌었을 때 페이지 전체가 아닌 해당 부분만 렌더링 시킬 수 있다.

2. 의도하지 않은 UI/UX를 보여주지 않을 수 있다.

3. 네트워크 통신 횟수를 줄일 수 있다.

 

리액트 상태 관리의 역사

2013 – Introduction
2014 – Flux (many libraries)
2015 – Redux
2016 – MobX
2018 – Context
2019 – Hooks Introduced (+ React Query, SWR)
2019 – Zustand
2019 – xState
2020 – Jotai, Recoil, Valtio
2021 – useSelectedContext

 

1. Flux 패턴

웹 애플리케이션의 규모가 커짐에 따라 상태(데이터)도 많아졌다. 이에 어디서 어떤 일이 일어나서 이 상태가 변했는지 등을 추적하고 이해하기가 매우 어려운 상황이었다. 기존 MVC 패턴은 모델과 뷰가 많아질수록 복잡도가 증가한다.

 

페이스북 팀은 양방향 데이터 바인딩인 MVC와 결별하고 단방향으로 데이터 흐름을 변경하는 것을 제안했다. 이것이 Flux패턴의 시작이다. 단방향 데이터 흐름은 모두 액션이라는 한 방향으로 흐름이 줄어들므로 데이터의 흐름을 추적하기 쉽고 코드를 이해하기가 수월해진다. 또한, 리액트는 대표적인 단방향 데이터 바인딩을 기반으로 한 라이브러리였으므로 이러한 단방향 흐름을 정의하는 Flux 패턴과 잘 맞았다.

 

 

2. Redux

리덕스는 Flux 구조를 구현하기 위해 만들어진 라이브러리 중 하나였다. Flux store와 마찬가지로 Redux reducer는 애플리케이션 코드가 직접 데이터를 조작하는 대신 액션 객체로만 모든 데이터 변화를 묘사한다.

리덕스는 Elm 아케텍처를 도입했다. Flux는 데이터 흐름을 acton, dispatcher, store, view 4가지로 분류했는데 리덕스 Elm 아키텍처는 model, update, view 3가지로 분류한다. 리덕스는 하나의 상태 객체를 스토어에 저장해 두고, 이 객체를 업데이트하는 작업을 디스패치해 업데이트를 수행한다.

리덕스의 등장은 리액트 생태계에 많은 영향을 미친다. 하나의 글로벌 상태 객체를 통해 이 상태를 하위 컴포넌트에 전파할 수 있기 때문에 props를 깊이 내려주는 문제를 해결할 수 있었고, 스토어가 필요한 컴포넌트라면 단지 connect를 쓰면 스토어에 바로 접근할 수 있었다.

코드량이 많다는 단점이 있었지만 리액트와 리덕스는 거의 표준으로 자리 잡았다. 

 

 

3. Context API

Context API 는 props로 상태를 넘겨주지 않더라도 원하는 곳에서 Context Provider 가 주입하는 상태를 사용할 수 있게된다.

 

 

4. 훅의 탄생, React Query 와 SWR

ContextAPI가 선보인지 1년이 채 되지 않아 리액트는 16.8 버전에서 함수 컴포넌트에 사용할 수 있는 다양한 훅 API를 추가했다. 가장 큰 변경점은 state를 매우 쉽게 재사용 가능하도록 만들 수 있었다. 훅과 state의 등장으로 이전에는 볼 수 없었던 방식의 상태 관리 React Query와 SWR이 등장한다. 두 라이브러리 모두 외부에서 데이터를 불러오는 fetch를 관리하는 데 특화된 라이브러리이다. 문제는 불필요한 리렌더링이 발생한다는 점이다.

 

5. Recoil, Zustand, Jotai, Valtio

훅이라는 새로운 기능의 등장에 따라, 훅을 활용해 상태를 가져오거나 관리할 수 있는 다양한 라이브러리가 등장한다. 페이스북에서 만든 Recoil을 필두로, Jotail, Zustand, Valtio 등 다양한 라이브러리가 나왔다.

 

 

 상태 관리 라이브러리

 Zustand

사용법

- store 생성

// store.js
import { create } from 'zustand' // create로 zustand를 불러옵니다.

const useStore = create(set => ({
  bears: 0,
  increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 })
}))

export default useStore

 

- store에 생성한 useStore를 불러와서 사용

import useStore from '../store.js'

const App = () => {
  const { bears, increasePopulation, removeAllBears } = useStore(state => state)
  
  return (
    <>
      <h1>{bears} around here ...</h1>
      <button onClick={increasePopulation}>one up</button>
      <button onClick={removeAllBears}>remove all</button>
    </>
  )
}

 

- devtools로 디버깅

import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

const store = (set) => ({
  bears: 0,
  increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 })
})

const useStore = create(devtools(store))

export default useStoreㅍ

 

- middleware

// 불변성을 유지하는 상태 업데이트

import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

type State = {
  count: number
}

type Actions = {
  increment: (qty: number) => void
  decrement: (qty: number) => void
}

export const useCountStore = create<State & Actions>()(
  immer((set) => ({
    count: 0,
    increment: (qty: number) =>
      set((state) => {
        state.count += qty
      }),
    decrement: (qty: number) =>
      set((state) => {
        state.count -= qty
      }),
  })),
)
// 스토리지에 데이터를 저장하는 기능

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

export const useBearStore = create(
  persist(
    (set, get) => ({
      bears: 0,
      addABear: () => set({ bears: get().bears + 1 }),
    }),
    {
      name: 'food-storage', // name of the item in the storage (must be unique)
      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
    },
  ),
)

 

다른 상태관리 라이브러리와 비교

- Redux

: 적은 코드량과 간단한 문법

- Jotai

 

- Recoil

 

장점

1. 유연성과 간결성

Zustand는 어떠한 패턴에도 종속되지 않는 유연한 라이브러리이다. 이러한 유연성은 다른 상태 관리 라이브러리나 패턴과 쉽게 통합할 수 있게 해준다.

 

2. 불필요한 렌더링 최소화

Zustand는 상태 변화에 따라 불필효한 컴포넌트 렌더링을 최소화한다. 이는 대규모 애플리케이션에서 성능을 크게 향상시켜 준다.

 

3. 프로바이더가 없는 구조

ContextAPI나 Recoil과 같은 라이브러리는 앱을 프로바이더로 감싸야 한다. Zustand는 이러한 제약이 없어 앱의 렌더링 트리를 단순화시킬 수 있다.

 

4. SSR과의 호환성

Zustand는 SSR(서버사이드렌더링)을 지원하여 다른 라이브러리보다 설정이 덜 복잡하다.

 

5. 커뮤니티와 생태계

커뮤니티가 활발하여 버그 수정이나 새로운 기능 추가가 빠르게 이루어진다.

 

6. 작은 용량

Recoil이나 Redux보다 작은 번들 사이즈를 가진다.

'Programming > React' 카테고리의 다른 글

React Query(TanStack Query) 사용하기  (0) 2024.02.16
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함