리액트에서의 독립적인 상태 저장소: Redux와 Zustand (1)

최정환·2024년 1월 14일
0
post-thumbnail

최근 리액트 애플리케이션에서의 상태 관리는 다양한 라이브러리를 통해 진화하고 있습니다. Redux와 Zustand는 이 분야에서 가장 인기 있는 두 도구입니다.

Action, Reducer 등을 통해 상태관리하는 사용 방법과 동작 방식에 대해 알고 있지만 각 라이브러리가 store를 어떻게 사용하고 있는지 아키텍처의 구성은 어떻게 되어 있는지 궁금해 찾아본 포스트입니다.

특히 많이 사용하고 차이점이 분명한 Redux(Single Store, Provider O)와 Zustand(Muilti Store, Provider X)을 봅니다.

이 글에서는 Redux와 Zustand의 독립적인 저장소 관리 방식을 탐구하고, 공식 깃헙 코드 분석을 통해 이들의 작동 방식을 심층적으로 이해해보겠습니다.

Redux

Redux는 단일 글로벌 스토어를 통해 애플리케이션의 모든 상태를 관리합니다. React-Redux의 Provider 컴포넌트는 이 스토어를 컴포넌트 트리에 전달하는 매개체로 작동합니다.

여기서 핵심은 React의 Context API가 이 모든 상태를 하위 컴포넌트에게 전달하는 방식입니다. Redux의 스토어는 리액트 컴포넌트와 분리되어 있지만, 상태를 전달하고 업데이트하기 위해 Context API를 활용합니다.


공식 깃헙 코드

간단하게 store와 provider과 관련된 함수들만 확인해 봅니다.

1. Store와 연결 준비

⏰ 아직까진 리액트 렌더 트리와 완전 독립적인 저장소입니다.


function getContext(): Context<ReactReduxContextValue | null> {
  if (!React.createContext) return {} as any

  const contextMap = (gT[ContextKey] ??= new Map<
    typeof React.createContext,
    Context<ReactReduxContextValue | null>
  >())
  
  // React Context 생성
  let realContext = contextMap.get(React.createContext)
  
  // 싱글톤
  if (!realContext) {
    realContext = React.createContext<ReactReduxContextValue | null>(
      null as any,
    )
    if (process.env.NODE_ENV !== 'production') {
      realContext.displayName = 'ReactRedux'
    }
    contextMap.set(React.createContext, realContext)
  }

  return realContext
}

ReactReduxContextValue 타입에는 Redux로 부터 받은 타입인 Store가 존재합니다.

React의 context api를 사용해 context를 생성하는 걸 알 수 있습니다.

⭐️ 이 컨텍스트는 React 애플리케이션에서 Redux 스토어를 사용할 수 있도록 해주는 매커니즘입니다.

❌ 이 코드에서 Redux 스토어는 직접적으로 React의 Context API로 "만들어지지" 않습니다.

대신, Context 객체는 Redux 스토어를 포함하는 값(value)을 제공하는 역할을 하며, 이 Context 객체는 React의 Context API를 사용하여 생성됩니다.

즉, Redux 스토어는 이 Context 객체를 통해 React 컴포넌트에 전달될 수 있습니다.


2. Provider

Provider는 Redux 스토어를 받아 Context에 넣어주고, 이를 통해 하위 컴포넌트가 Context를 통해 스토어에 접근할 수 있도록 합니다.

🥳 리액트 렌더 트리와 독립적인 저장소 연결

function Provider<A extends Action<string> = UnknownAction, S = unknown>({
  store,
  context,
  children,
  serverState,
  stabilityCheck = 'once',
  identityFunctionCheck = 'once',
}: ProviderProps<A, S>) {
  
  const contextValue = React.useMemo(() => {
    const subscription = createSubscription(store)
    return {
      store,
      subscription,
      getServerState: serverState ? () => serverState : undefined,
      stabilityCheck,
      identityFunctionCheck,
    }
  }, [store, serverState, stabilityCheck, identityFunctionCheck])

  const previousState = React.useMemo(() => store.getState(), [store])

  useIsomorphicLayoutEffect(() => {
    // Redux 스토어의 상태 변화를 구독
    const { subscription } = contextValue
    
    // onStateChange : 상태가 변경되었을 때 호출될 콜백 함수
    // notifyNestedSubs : 상태가 다를 경우, 구독자들에게 상태 변화를 알림
    subscription.onStateChange = subscription.notifyNestedSubs
    // 스토어에 대한 구독을 시작
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
    return () => {
      subscription.tryUnsubscribe()
      subscription.onStateChange = undefined
    }
  }, [contextValue, previousState])

  // 만약 지정한 context를 주지 않으면 store의 context 사용
  const Context = context || ReactReduxContext

  // React Context 사용 방법
  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

저희가 일반적으로 사용하는 props들은 store, children 그리고 context만 보면서 지나갑니다.

store: Redux 스토어 인스턴스입니다.
context: 선택적으로 사용할 수 있는 React 컨텍스트 객체입니다. 지정하지 않으면 react-redux의 기본 컨텍스트가 사용됩니다.
children: Provider 컴포넌트 내부에 렌더링될 React 노드들입니다.

useIsomorphicLayoutEffect: DOM을 사용할 수 있다면 React.useEffect, 없다면 React.useLayoutEffect입니다. 구독을 시작하고, 컴포넌트가 언마운트될 때 구독을 취소합니다.

이 구현을 통해 react-redux는 컴포넌트 트리에 Redux 스토어를 효과적으로 제공하며, 스토어의 상태 변화를 구독하는 로직을 관리합니다.

이렇게 React 컴포넌트와 Redux 스토어 사이의 연결을 유지하면서 상태 관리의 일관성과 효율성을 보장합니다.


결론

Redux 자체는 리액트와 직접 연동하지 않고 독립적으로 사용할 수 있는 상태 관리 라이브러리입니다.

React에서 사용하기 위해서는 환경에 맞는 특별한 통합 방식이나 라이브러리가 필요합니다. 다른 View 프레임워크도 마찬가지입니다.




https://github.com/reduxjs/react-redux/blob/master/src/components/Context.ts

https://github.com/reduxjs/react-redux/blob/master/src/components/Provider.tsx

0개의 댓글