---
# System prepended metadata

title: 使用 Typescript 寫 xstate 相關筆記
tags: [xstate]

---

# 使用 Typescript 寫 xstate 相關筆記

###### tags: `xstate`

此筆記始於重構某 js 專案，想慢慢改寫成 typescript，把 xstate 相關筆記集中於此。

## 定義 service function 參數 `event` 型別

:::info
xstate version: 4.13.0
:::

假設有一個 machine，預期會接收以下 event (`SomeMachineEvent`)：

```typescript=
enum SomeMachineEventTypeEnum {
  AAA = 'AAA',
  BBB = 'BBB'
}

type SomeMachineEvent =
  | { type: SomeMachineEventTypeEnum.AAA }
  | { type: SomeMachineEventTypeEnum.BBB, value: string }
```

在定義 service function (`handleBBB`) 時，
預期只有觸發 `SomeMachineEventTypeEnum.BBB` event 情況才會執行此 service，
直覺上會想這樣寫：

```typescript=
const machine = Machine(config, {
  services: {
    handleBBB: (context, event: SomeMachineEvent & { type: SomeMachineEventTypeEnum.BBB }) => {
      // do something
    }
  }
})
```

但因 xstate 對於 service function 的型別有嚴格規定，不允許這樣定義型別，
xstate 作者有提供[一種做法](https://github.com/davidkpiano/xstate/discussions/1591)解決這問題：

```typescript=
function assertEventType<TE extends EventObject, TType extends TE["type"]>(
  event: TE,
  eventType: TType
): asserts event is TE & { type: TType } {
  if (event.type !== eventType) {
    throw new Error(
      `Invalid event: expected "${eventType}", got "${event.type}"`
    );
  }
}
```

```diff=
        setClaims: assign({
          claims: (_ctx, evt) => {
+           assertEventType(evt, "LOGIN");
            return evt.value;
          }
        })
```

此做法其實相當於直接寫 if-else statement 告訴 typescript eventObject 是什麼型別，
如果不是對應的型別，直接 throw error 中斷流程。
但筆者不喜歡用 runtime 檢查，於是改寫另一種作法：

```typescript=
type AssertEventType<EventType extends SomeMachineEventTypeEnum> = SomeMachineEvent & { type: EventType }
```

```typescript=
const machine = Machine(config, {
  services: {
    handleBBB: (context, event) => {
      const { value } = event as AssertEventType<SomeMachineEventTypeEnum.BBB>
      // do something with value
    }
  }
})
```

### 後續

重構過程中發現有可能需要在 `SomeMachineEvent` 加入從 `invoke.onDone` 事件傳來的 eventObject 型別 `DoneInvokeEvent<TData>` (from xstate)，所以後來又在擴充 `AssertEventType` 加入判別是否為 `DoneInvokeEvent` 的功能。