-
React에서 XState 간단 사용 후기 😊React 2025. 2. 13. 22:58

XState는 점점 더 단순화되어가는 상태관리 라이브러리들 중에서 좀 더 명확하게 역할을 나누고 책임을 부여한 컨셉의 상태관리 라이브러리이다.
❗ 이런 특징이 redux의 구조와 비슷하게 느껴지기도 한데, 이에 관해서는 직접 제작자가 밝힌 차이점이 있다.
https://stackoverflow.com/questions/54482695/what-is-an-actual-difference-between-redux-and-a-state-machine-e-g-xstateWhat is an actual difference between redux and a state machine (e.g. xstate)?
I am working on investigation of one front-end application of medium complexity. At this moment it is written in pure javascript, it has a lot of different event-based messages connecting few main ...
stackoverflow.com
위의 글과 공식문서도 참조하고, 간단하게 예제를 사용하여 XState를 사용해본 뒤 XState는 어떤 장점으로 선택할지 고민해봤다 🙌
XState 사용 예제 (모달 상태 관리) 🙏
실전적인 예제를 작성하기 위해서, 모달을 만들 때 XState를 활용해보았다.
(🍔 예제에 사용된 버전은 "xstate": "^5.19.2, "@xstate/react": "^5.0.2" 입니다)npm install xstate @xstate/reactyarn add xstate @xstate/reactreact에서 활용하기 위해서 hook을 제공해주는 @xstate/react도 함께 설치한다.
그리고 타입스크립트를 사용한다면, 다음과 같이 tsconfig.json 설정을 해주자.
(xstate의 권장사항이다){ compilerOptions: { strictNullChecks: true, skipLibCheck: true, }, }
타입스크립트 여부에 따라서 코드 작성도 좀 달라지는데, typescript를 사용하지 않는다면 createMachine으로 충분하지만, typescript 사용시 setup를 사용해 createMachine을 하는 것을 추천하고 있다.
(createMachine 에서도 타입을 작성할 수 있긴 하다 😅)import { createMachine } from 'xstate'; // js export const modalMachine = createMachine({ id: 'modal', initial: 'close', states: { close: { on: { OPEN: 'opene' }, }, open: { on: { CLOSE: 'close' }, }, }, });js에서는 이렇게 작성해도 괜찮지만, ts에서는 아래처럼 작성하는 것이 권장사항이다.
import { assign, setup } from 'xstate'; export const modalMachine = setup({ types: { context: {} as { isOpen: boolean }, events: {} as { type: 'close' } | { type: 'open' }, }, actions: { setOpen: assign({ isOpen: (_ctx, _event) => true }), setClose: assign({ isOpen: (_ctx, _event) => false }), }, }).createMachine({ context: { isOpen: false }, initial: 'closed', states: { closed: { on: { open: { target: 'open', actions: 'setOpen' } }, }, open: { on: { close: { target: 'closed', actions: 'setClose' } }, }, }, });
위와 같이 코드를 작성하고, 실제 해당 상태를 사용하는 페이지를 아주 간단하게 만들면 다음과 같이 모달 컴포넌트 상태를 작성할 수 있다.'use client'; import { useMachine } from '@xstate/react'; import { modalMachine } from './store'; const ModalXstate = () => { const [state, send] = useMachine(modalMachine); return ( <div> <button onClick={() => send({ type: 'open', }) } > 모달 열기 </button> {state.context.isOpen && ( <div className="modal"> <p>모달 내용</p> <button onClick={() => send({ type: 'close', }) } > 모달 닫기 </button> </div> )} </div> ); }; export default ModalXstate;
이제 코드를 작동해보면, 실제로 잘 작동함을 확인할 수 있다.
요즘 많이 선택되는 zustand나 jotai와 비교하면, 생각보다 초기 설정이 복잡하다.
그렇다면, 왜 그렇게 복잡한 xstate를 사용해야 할까?
하는 생각이 절로 드는데, 이에 대해서 조사한뒤 직접 사용하고 생각이 난 점들에 대해서 정리해봤다.
요약하자면, 다양한 상태변화가 있는 복잡한 상태관리 로직이 있을 때, XState를 사용하면 명확하게 상태를 관리할 수 있다 👍XState를 선택해야 하는 이유 ✨
- 명확한 상태 모델링
- 상태를 Finite State Machine(FSM, 유한 상태 기계) 개념으로 관리하기 때문에, 컴포넌트가 어떤 상태에 있을지 한눈에 파악할 수 있다.
- 예를 들어, 모달이 open인지 close인지, 버튼이 loading인지, success인지 상태가 확실하게 정의된다.
- 상태를 Finite State Machine(FSM, 유한 상태 기계) 개념으로 관리하기 때문에, 컴포넌트가 어떤 상태에 있을지 한눈에 파악할 수 있다.
- 이벤트 기반 전이 (Event-Driven State Transitions)
- onClick 같은 이벤트를 단순히 상태 변경하는 게 아니라, 이벤트를 기반으로 상태가 변한다.
- 예를 들어, FETCH 이벤트가 발생하면 loading -> success | failure 상태로 이동할 수 있다.
- onClick 같은 이벤트를 단순히 상태 변경하는 게 아니라, 이벤트를 기반으로 상태가 변한다.
- Redux보다 간결한 상태 관리
- Redux처럼 전역 상태 관리가 필요한 경우에도 사용 가능하지만, Redux 보다는 코드가 훨씬 간결하다.
- Reducer에서 switch-case 남발하는 대신, 한눈에 보기 좋은 트랜지션으로 정리할 수 있다.
- Redux처럼 전역 상태 관리가 필요한 경우에도 사용 가능하지만, Redux 보다는 코드가 훨씬 간결하다.
- 비동기 작업 (Promise & Actor) 관리 편리
- invoke를 사용하면 비동기 API 호출을 자연스럽게 상태 흐름에 녹일 수 있다.
- useEffect 없이도 비동기 처리 가능해서, 클린한 코드를 유지할 수 있다.
- invoke를 사용하면 비동기 API 호출을 자연스럽게 상태 흐름에 녹일 수 있다.
- 불가능한 상태 방지
- Redux에서는 개발자가 직접 불가능한 상태(예: loading 상태에서 reset이 호출되는 경우)를 방지해야 하지만, XState는 Statechart 개념을 사용하여 자연스럽게 이를 차단한다.
- 상태 전이 시각화 가능
- XState는 Statechart Visualizer 를 제공하여 상태 전이를 시각적으로 확인할 수 있다.
- 구조적인 상태 관리
- Redux는 전역 상태를 관리하는 방식이지만, XState는 여러 개의 독립적인 상태 머신을 Actor 모델 기반으로 구성할 수 있어, 복잡한 UI에서도 확장성이 뛰어나다.
4번에서 비동기 상태 호출에 대해서 invoke를 언급했는데, 실제 invoke의 작동을 테스트해본 코드는 다음과 같다.import { assign, fromPromise, setup } from 'xstate'; type ApiResult = { userId: number; id: number; title: string; completed: boolean; }; const fakeApiCall = (userId: number): Promise<ApiResult> => fetch(`https://jsonplaceholder.typicode.com/todos/${userId}`).then((response) => response.json()); export const todoMachine = setup({ types: { context: {} as { result: ApiResult; error: unknown; }, // FETCH 이벤트에 userId가 포함되고, RETRY 이벤트도 정의합니다. events: {} as { type: 'FETCH'; userId: number } | { type: 'RETRY' }, }, actors: { // fromPromise를 사용하여 비동기 actor를 정의합니다. // 이 actor는 input으로 { userId: number } 타입의 값을 받습니다. fetchTodo: fromPromise<unknown, { userId: number }>(async ({ input }) => { const user = await fakeApiCall(input.userId); return user; }), }, }).createMachine({ id: 'todo', initial: 'idle', context: { result: { userId: 1, id: 0, title: '', completed: false, }, error: undefined, }, states: { idle: { on: { FETCH: { target: 'loading' }, }, }, loading: { invoke: { id: 'getTodo', src: 'fetchTodo', // event.type이 FETCH일 때는 event.userId를 input으로 사용하고, 그렇지 않으면 userId를 1로 사용합니다. input: ({ event }) => { if (event.type === 'FETCH') { return { userId: event.userId }; } return { userId: 1 }; }, onDone: { target: 'success', actions: assign({ result: ({ event }) => event.output as ApiResult, }), }, onError: { target: 'failure', actions: assign({ error: ({ event }) => event.error, }), }, }, }, success: {}, failure: { on: { RETRY: { target: 'loading' }, }, }, }, });위와 같이 상태를 정의하고,
<div> <button onClick={() => { sendApi({ type: 'FETCH', userId: 1, }); }} > Call Api </button> {apiState.value !== 'success' ? ( <div>Loading...</div> ) : ( <div> <h1>API RESULT</h1> <p>{apiState.context.result.title}</p> </div> )} </div>위와 같이 호출했다.
value 값에 loading, success와 같은 값이 들어가므로, 자연스럽게 api 로딩에 따른 화면 지연을 표현할 수 있다.🚀 XState 요약
✅ 장점
- 복잡한 상태 관리가 눈에 보이게 정리됨
- useEffect 없이 비동기 작업 가능
- 불가능한 상태가 원천적으로 차단됨
- 상태 전이를 시각적으로 확인 가능
❌ 단점
- 처음 배우는 데 약간의 러닝 커브가 있음 😵💫
- 간단한 상태 관리라면 useState보다 오버킬일 수 있음
- 현재 디버깅툴이 설치가 안됨?? (DevTools 크롬 익스텐션이 설치가 안됨....)
결론적으로, XState는 복잡한 상태를 다룰 때 진짜 강력한 도구이다! 🎯
단순한 상태 관리라면 필요 없지만, 다양한 상태 전이가 필요한 UI에서는 고려할만하다! 🚀
+ 다만, 이 글에서는 아주 단순한 예제로 xstate를 다뤄보았고, 모든 기능을 사용해본 것이 아니기에 좀 더 지속적으로 xstate를 다뤄보고 좀 더 테스트해볼 필요가 있을 것 같다.
👻 작성한 예제 코드
https://github.com/citron03/practice-next-15/tree/main/app/modal-xstatepractice-next-15/app/modal-xstate at main · citron03/practice-next-15
Practice React 19, Next 15 (with react compiler). Contribute to citron03/practice-next-15 development by creating an account on GitHub.
github.com
😊 참고 자료
https://stately.ai/docs/quick-startQuick start | Stately
Start here to jump straight into XState and Stately Studio.
stately.ai
https://stately.ai/docs/xstate-react
@xstate/react | Stately
The @xstate/react package contains hooks and helper functions for using XState with React.
stately.ai
https://stately.ai/docs/typescript#set-up-your-tsconfigjson-file
TypeScript | Stately
XState v5 and its related libraries are written in TypeScript, and utilize complex types to provide the best type safety and inference possible for you.
stately.ai
https://ko.wikipedia.org/wiki/%EC%9C%A0%ED%95%9C_%EC%83%81%ED%83%9C_%EA%B8%B0%EA%B3%84
유한 상태 기계 - 위키백과, 우리 모두의 백과사전
위키백과, 우리 모두의 백과사전. 오토마타 벤 다이어그램 (각 레이어를 클릭하면 해당 글로 이동합니다.) 유한 상태 기계(finite-state machine, FSM) 또는 유한 오토마톤(finite automaton, FA; 복수형: 유한
ko.wikipedia.org
'React' 카테고리의 다른 글
MDX 설정하기 in Next.js 15 🍝 (Markdown for thecomponent era) (0) 2025.05.18 React를 사용할 때, HTML을 직접 삽입하기 - dangerouslySetInnerHTML 사용 예제 (0) 2025.04.20 React Compiler를 직접 사용해보기 🎶 (with. next 15) (1) 2024.12.10 React Context API 잘 사용하기 (re-render 방지법 - selector & atom) (0) 2024.07.24 React JSX의 List Rendering에서 key값 설정하기 (.map) (1) 2023.09.23 - 명확한 상태 모델링