XState 및 React로 전역 상태를 관리하는 방법

12897 단어 webdevxstatereduxreact

This article has become part of the official XState docs!



많은 React 애플리케이션은 Redux으로 대중화된 Flux 아키텍처를 따릅니다. 이 설정은 몇 가지 주요 아이디어로 특징지을 수 있습니다.
  • 스토어라고 하는 모든 애플리케이션 상태를 저장하는 앱 상단의 단일 객체를 사용합니다.
  • 상점까지 메시지를 보내는 데 사용할 수 있는 단일dispatch 기능을 제공합니다. Redux는 이것을 actions라고 부르지만 XState에서 알려진 대로 events라고 부르겠습니다.
  • 스토어가 앱의 이러한 메시지에 응답하는 방식은 대부분 리듀서에서 순수 함수로 표현됩니다.

  • 이 기사는 Flux 아키텍처가 좋은 생각인지에 대해서는 깊이 다루지 않습니다. David Khourshid의 기사는 여기에서 자세히 설명합니다. 이 문서의 목적을 위해 글로벌 스토어를 갖고 싶어하고 XState에서 이를 복제하려고 한다고 가정합니다.

    그렇게 하고 싶은 데에는 여러 가지 이유가 있습니다. 복잡한 비동기 동작을 관리하고 어려운 문제를 모델링할 때 XState는 누구에게도 뒤지지 않습니다. Redux 앱에서 이를 관리하려면 일반적으로 redux-thunk , redux-loop 또는 redux-saga 미들웨어가 필요합니다. XState를 선택하면 복잡성을 관리할 수 있는 최고의 방법이 제공됩니다.

    전 세계적으로 사용 가능한 매장



    Redux의 전 세계적으로 사용 가능한 저장소를 모방하기 위해 React 컨텍스트를 사용할 것입니다. React 컨텍스트는 작업하기 까다로운 도구일 수 있습니다. 너무 자주 변경되는 값을 전달하면 트리 아래로 다시 렌더링될 수 있습니다. 즉, 가능한 한 적게 변경되는 값을 전달해야 합니다.

    운 좋게도 XState는 이를 위한 최고의 방법을 제공합니다.

    import React, { createContext } from 'react';
    import { useInterpret } from '@xstate/react';
    import { authMachine } from './authMachine';
    import { ActorRefFrom } from 'xstate';
    
    interface GlobalStateContextType {
      authService: ActorRefFrom<typeof authMachine>;
    }
    
    export const GlobalStateContext = createContext(
      // Typed this way to avoid TS errors,
      // looks odd I know
      {} as GlobalStateContextType,
    );
    
    export const GlobalStateProvider = (props) => {
      const authService = useInterpret(authMachine);
    
      return (
        <GlobalStateContext.Provider value={{ authService }}>
          {props.children}
        </GlobalStateContext.Provider>
      );
    };
    

    useInterpret를 사용하면 구독할 수 있는 실행 중인 시스템에 대한 정적 참조인 service가 반환됩니다. 이 값은 절대 변경되지 않으므로 낭비되는 재렌더링에 대해 걱정할 필요가 없습니다.

    컨텍스트 활용



    트리 아래에서 다음과 같이 서비스에 가입할 수 있습니다.

    import React, { useContext } from 'react';
    import { GlobalStateContext } from './globalState';
    import { useActor } from '@xstate/react';
    
    export const SomeComponent = (props) => {
      const globalServices = useContext(GlobalStateContext);
      const [state] = useActor(globalServices.authService);
    
      return state.matches('loggedIn') ? 'Logged In' : 'Logged Out';
    };
    

    useActor 후크는 서비스가 변경될 때마다 수신하고 state 값을 업데이트합니다.

    성능 향상



    위의 구현에 문제가 있습니다. 이렇게 하면 서비스 변경 사항에 대한 구성 요소가 업데이트됩니다. Redux는 구성 요소를 다시 렌더링할 수 있는 상태 부분을 제한하는 기능인 선택기를 사용하여 상태를 유도하는 도구를 제공합니다.

    운 좋게도 XState도 이를 제공합니다.

    import React, { useContext } from 'react';
    import { GlobalStateContext } from './globalState';
    import { useSelector } from '@xstate/react';
    
    const selector = (state) => {
      return state.matches('loggedIn');
    };
    
    export const SomeComponent = (props) => {
      const globalServices = useContext(GlobalStateContext);
      const isLoggedIn = useSelector(globalServices.authService, selector);
    
      return isLoggedIn ? 'Logged In' : 'Logged Out';
    };
    


    이제 이 구성 요소는 state.matches('loggedIn')가 다른 값을 반환하는 경우에만 다시 렌더링됩니다. 이것은 성능을 최적화하려는 경우 useActor에 대한 권장 접근 방식입니다.

    이벤트 전달



    전역 저장소에 이벤트를 전달하기 위해 서비스의 send 함수를 직접 호출할 수 있습니다.

    import React, { useContext } from 'react';
    import { GlobalStateContext } from './globalState';
    
    export const SomeComponent = (props) => {
      const globalServices = useContext(GlobalStateContext);
    
      return (
        <button onClick={() => globalServices.authService.send('LOG_OUT')}>
          Log Out
        </button>
      );
    };
    


    이를 위해 useActor를 호출할 필요가 없으며 컨텍스트에서 바로 사용할 수 있습니다.

    플럭스와의 편차



    예리한 독자라면 이 구현이 Flux와 약간 다르다는 것을 알아차릴 수 있습니다. 예를 들어 단일 전역 저장소 대신 authService , dataCacheServiceglobalTimeoutService 와 같이 한 번에 여러 대의 실행 중인 시스템이 있을 수 있습니다. 각각 고유한 속성send도 있으므로 글로벌 디스패치를 ​​호출하지 않습니다.

    이러한 변경 사항은 해결할 수 있습니다. 모든 서비스send 기능을 수동으로 호출하는 글로벌 스토어 내부에 합성send을 생성할 수 있습니다. 그러나 개인적으로 나는 내 메시지가 전달되는 서비스를 정확히 아는 것을 선호하며 이벤트를 전역적으로 네임스페이스로 유지하지 않아도 됩니다.

    요약



    XState는 React 애플리케이션의 글로벌 저장소로 훌륭하게 작동할 수 있습니다. 응용 프로그램 논리를 같은 위치에 유지하고 부작용을 일급 시민으로 취급하며 useSelector와 함께 우수한 성능을 제공합니다. Flux 아키텍처에 관심이 있지만 앱의 논리가 감당할 수 없다고 생각되는 경우 이 접근 방식을 선택해야 합니다.

    좋은 웹페이지 즐겨찾기