๐ ๏ธ ์ฒ์๋ถํฐ ๋ง๋๋ State Machine โ ์ํ ์ ์ด๋ฅผ ์ฝ๋๋ก ์ ์ดํ๊ธฐ
๋ฌธ์
๋ฒํผ ํ๋์ `isLoading`, `isError`, `isSuccess`, `isIdle` ํ๋๊ทธ 4๊ฐ๊ฐ ๋ถ์ด ์๋ค๋ฉด?
```js
// โ ํ๋๊ทธ ์ง์ฅ โ ๋์์ isLoading=true, isError=true๊ฐ ๋ ์ ์๋ค
let isIdle = true;
let isLoading = false;
let isError = false;
let isSuccess = false;
```
๋ถ๊ฐ๋ฅํ ์ํ ์กฐํฉ์ด ์๊ธฐ๊ณ , ์กฐ๊ฑด๋ฌธ์ด ๊ธฐํ๊ธ์์ ์ผ๋ก ๋์ด๋ฉ๋๋ค. State Machine์ "ํ์ฌ ์ํ์์ ์ด ์ด๋ฒคํธ๊ฐ ์ค๋ฉด โ ๋ค์ ์ํ๋ ์ด๊ฒ"์ด๋ผ๋ ๊ท์น์ ๋ช
์ํด์ ์ด ๋ฌธ์ ๋ฅผ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
---
์ฝ๋ โ 60์ค State Machine
```js
function createMachine(config) {
let currentState = config.initial;
const listeners = new Set();
function transition(event) {
const stateConfig = config.states[currentState];
if (!stateConfig) throw new Error(`Unknown state: ${currentState}`);
const transitionConfig = stateConfig.on?.[event];
if (!transitionConfig) return currentState; // ํ์ฉ๋์ง ์์ ์ด๋ฒคํธ๋ ๋ฌด์
const nextState = typeof transitionConfig === 'string'
? transitionConfig
: transitionConfig.target;
// exit action ์คํ
stateConfig.exit?.();
// transition action ์คํ
if (typeof transitionConfig === 'object') {
transitionConfig.action?.();
}
currentState = nextState;
// enter action ์คํ
config.states[currentState]?.enter?.();
// ๊ตฌ๋
์์๊ฒ ์๋ฆผ
listeners.forEach(fn => fn(currentState));
return currentState;
}
function getState() {
return currentState;
}
function subscribe(fn) {
listeners.add(fn);
return () => listeners.delete(fn);
}
// ์ด๊ธฐ ์ํ enter action
config.states[currentState]?.enter?.();
return { transition, getState, subscribe };
}
```
---
์ฌ์ฉ ์์ โ Fetch ์ํ ๊ด๋ฆฌ
```js
const fetchMachine = createMachine({
initial: 'idle',
states: {
idle: {
on: { FETCH: 'loading' },
enter: () => console.log('โ ๋๊ธฐ ์ค'),
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
enter: () => console.log('โ ๋ก๋ฉ ์ค...'),
},
success: {
on: { RESET: 'idle' },
enter: () => console.log('โ ์ฑ๊ณต!'),
},
error: {
on: {
RETRY: { target: 'loading', action: () => console.log('์ฌ์๋!') },
RESET: 'idle',
},
enter: () => console.log('โ ์๋ฌ ๋ฐ์'),
},
},
});
// ์ํ ๋ณํ ๊ตฌ๋
const unsubscribe = fetchMachine.subscribe(state => {
console.log(`[๊ตฌ๋
] ํ์ฌ ์ํ: ${state}`);
});
fetchMachine.transition('FETCH'); // โ ๋ก๋ฉ ์ค...
fetchMachine.transition('ERROR'); // โ ์๋ฌ ๋ฐ์
fetchMachine.transition('RETRY'); // ์ฌ์๋! โ ๋ก๋ฉ ์ค...
fetchMachine.transition('SUCCESS'); // โ ์ฑ๊ณต!
fetchMachine.transition('FETCH'); // ๋ฌด์๋จ (success์์ FETCH ๋ถ๊ฐ)
fetchMachine.transition('RESET'); // โ ๋๊ธฐ ์ค
console.log(fetchMachine.getState()); // 'idle'
unsubscribe();
```
---
ํต์ฌ ์ค๋ช
| ๊ฐ๋
| ์ค๋ช
|
|------|------|
| State (์ํ) | `idle`, `loading` ๋ฑ ์์คํ
์ด ์กด์ฌํ ์ ์๋ ๋ชจ๋ |
| Event (์ด๋ฒคํธ) | `FETCH`, `SUCCESS` ๋ฑ ์ํ ์ ์ด๋ฅผ ์ผ์ผํค๋ ํธ๋ฆฌ๊ฑฐ |
| Transition (์ ์ด) | "์ด ์ํ์์ ์ด ์ด๋ฒคํธ โ ์ ์ํ"๋ผ๋ ๊ท์น |
| Action (์ก์
) | ์ ์ด ์ ์คํ๋๋ ๋ถ์ ํจ๊ณผ (`enter`, `exit`, `action`) |
์ ์ข์๊ฐ?
1. ๋ถ๊ฐ๋ฅํ ์ํ๊ฐ ์์ฒ ์ฐจ๋จ โ `loading`์ด๋ฉด์ `success`์ธ ์ํ๋ ์กด์ฌ ๋ถ๊ฐ
2. ํ์ฉ๋์ง ์์ ์ ์ด ๋ฌด์ โ `success`์์ `FETCH`๋ฅผ ๋ณด๋ด๋ ์๋ฌด ์ผ ์์
3. ์๊ฐํ ๊ฐ๋ฅ โ ์ํ ๋ค์ด์ด๊ทธ๋จ์ผ๋ก ๋ฐ๋ก ๊ทธ๋ฆด ์ ์๋ ๊ตฌ์กฐ
---
ํ๋ก๋์
์์๋?
์ง์ ๊ตฌํ ๋์ XState๋ฅผ ์ฌ์ฉํ์ธ์. ์ค์ฒฉ ์ํ, ๋ณ๋ ฌ ์ํ, guard, context ๋ฑ ์ค๋ฌด์ ํ์ํ ๊ธฐ๋ฅ์ ๋ชจ๋ ์ ๊ณตํฉ๋๋ค.
```bash
npm install xstate
```
๐ ๊ณต์ ๋ฌธ์: [https://stately.ai/docs/xstate](https://stately.ai/docs/xstate)
> ์ํ๋ฅผ boolean ํ๋๊ทธ๋ก ๊ด๋ฆฌํ๊ณ ์๋ค๋ฉด, State Machine ๋์
์ ๊ณ ๋ คํด ๋ณด์ธ์. ๋ฒ๊ทธ๊ฐ ์ค๊ณ , ์ฝ๋๊ฐ ์ฝ๊ธฐ ์ฌ์์ง๋๋ค.