狀態機(State Machine)
上一篇文章跟大家討論了關於「流程設計」這檔事,不知道是否有幫助到大家。有沒有開始開始從那種「想到什麼就直接寫」的直覺式開發,慢慢轉變成有條理、規劃性的開發方式,讓整體專案架構更加穩定,也減少那些莫名其妙、難以排查的錯誤呢?
狀態機(State Machine)
上一篇文章跟大家討論了關於「流程設計」這檔事,不知道是否有幫助到大家。有沒有開始開始從那種「想到什麼就直接寫」的直覺式開發,慢慢轉變成有條理、規劃性的開發方式,讓整體專案架構更加穩定,也減少那些莫名其妙、難以排查的錯誤呢?
而這次想跟大家介紹的,是我個人在實際應用上很常用到的一個概念,一個能讓「流程」更加穩定、清晰且可預測的概念 — 狀態機(State Machine)。

我們可以透過簡單的布林值來達到一般的流程控制,但是一旦專案變大,整個狀況變得相當複雜時,我們可能就會陷入以下僵局:
一個階段同時會有多筆布林值做判斷是否可以進入,進入階段後達成特定條件要修改某些布林值能進入下一階段,光是這樣想想就會知道其實這樣管理很容易混亂及出現 Bug。
於是乎就有了狀態機來解決這些問題
流程設計之初
在我們的專案設計這篇文章中,提到流程設計的核心目的,其實就是為了:
- 讓複雜邏輯變得更可控
- 明確定義不同的「狀態(State)」與「階段(Stage)」
- 確保整體流程的順序正確性
然而在實際開發時,當我們想要做到這些事情,最直覺、也最常使用的方式,通常都是透過一種資料型態來判斷 —布林值(Boolean)。
使用布林值來做管理狀態,其實非常直覺且方便。以下我用一個相當常見的情境來做舉例以及試想:會員登入流程。
使用布林值管理狀態
今天有一位使用者準備進入登入頁面。 此時,我們可能會先檢查:
「這位使用者是否已經登入過了?」
在實務上,若用布林值做管理,我們通常會使用類似以下的狀態來判斷:
// 變數名稱可隨意
const isLogin = true
or
const isAuthenticated = false
接著再根據這個布林值,決定使用者現在可以顯示什麼或是接下來可以做什麼。例如:
isLogin(isAuthenticated)= true
- 隱藏登入入口
- 將使用者轉導回首頁
isLogin(isAuthenticated)= false
- 允許進入登入頁面
- 顯示登入表單
透過這樣簡單的布林值判斷,其實就已經能完成基礎的流程控制了。
問題開始發生的時候
但真正的問題,往往會在專案逐漸變大變複雜之後開始出現。
因為實際上真正的流程判斷,通常不會只有用單一 true / false 來做判別,而是可能同時存在多種情況,例如:
//下一階段達成條件
isLogin = ture
isLoading= false
isError= false
isExpired= false
isVerify = ture
isAdmin = false
這時候你就會發現,再設計流程中會需要考慮到:
- 一個頁面可能需要同時判斷多個布林值
- 某些狀態之間彼此互相依賴或是排斥
- 不同流程可能會依賴相同的狀態
- 某些條件組合不應該同時存在,發生時應進入錯誤流程處理
布林值開始失控
簡單舉例,根據參數名稱的語意,我們不希望可以再流程中看到以下的狀態出現:
isLoading = true
isError = true
isSuccess = true
因為這代表:
- 系統目前正處在 Loading 狀態
- 此時發生 Error
- 但又莫名其妙 Success
很明顯,這些狀態彼此之間是衝突的
為什麼使用布林值容易失控?
這就回到布林值這個資料型態的本質上了。
布林值只能表達-true / false(是 / 否)。但實際上的流程狀態,往往遠比單一的是或否來的更複雜。
例如一個登入流程,實際上可能包含多種可能發生狀態:
- loading(登入中)
- success(登入成功)
- error(登入失敗)
- expired(登入過期)
以登入階段流程中,這些狀態其實是「互斥」的,也就是同一時間只應該存在一種狀態。但我們如果全部都依靠布林值管理,就很容易出現狀態衝突、狀態遺漏、難以追蹤等等不同情況
而這也正是為什麼,後來會開始出現「狀態機(State Machine)」這種設計方式,來幫助我們更有系統地管理流程與狀態。
狀態機的設立流程
第一步:先列流程
與流程設計類似,很多時候我們最容易犯的錯就是,一看到需求就想要直接解決。首先,先不要急著寫程式,我們先根據需求列出流程需求
以登入流程為例,很多人第一直覺可能會寫成:
登入
→ 成功
→ 失敗
但如果先停下來分析需求,你會發現登入流程其實還有很多狀態需要考慮,例如:
loading
success
error
expired
第二步:確認「唯一狀態」
狀態機真正重要的概念是:「同一時間,只能存在一種主要狀態」。
正因為這個概念,狀態機才能真正幫助我們避免流程混亂與狀態衝突。所以在設計流程時,我們需要先思考:
- 目前這個階段會有哪些狀態?
- 哪些狀態是互斥的?
假設沒有定義好狀態,可能會讓多種狀態同時成立,容易發生以下情形:
isLoading = true;
isSuccess = true;
isError = false;
isExpired = false;
由於布林值彼此獨立,因此程式本身並不會阻止你這樣設定,但從邏輯上來看,實際上 Loading 與 Success 同時存在是不合理的。這其實是布林值管理流程時,最容易失控的地方之一。
其實我們真正需要思考的是,在這階段真正該存在的狀態,也就是目前處於哪個狀態,例如:
type State =
| "loading"
| "success"
| "error"
| "expired"
這代表現在狀態是:
- 登入中
- 登入成功
- 登入失敗
- 登入過期
而不是多個狀態同時存在。
第三步:定義「能去哪裡」
這是很多人在設計流程時最容易忽略的一件事。很多人了解狀態機後,只顧著定義當前狀態,但真正完整的狀態機應該還需要包含
- 狀態之間如何切換
- 狀態是否有依賴性
- 哪種狀態不能做轉換
- 狀態之間是雙向的還是單向的
我還是登入流程來做舉例,我們可能會有以下狀態:
loading
→ success
→ error
但我們一般在實作之中,可能還會加入 Idle 狀態,因此實際上整體流程會是以下:
loading → success → idle
→ error ↗
這其實代表著:
- loading 完成後成功會進入 success,錯誤會進入 error
- 在確認登入後的狀態後,就都會進入 Idle 狀態
而這件事情,在狀態機中有一個非常重要的名稱-狀態轉移(Transition)
狀態轉移的核心目的,就是:
- 限制狀態彼此如何切換
- 避免非法流程及不合邏輯的轉換出現
- 讓流程整體更加可預測
換句話說:
狀態機不只是單單定義「現在在哪」, 更重要的是定義「接下來可以去哪」
狀態機設立時的重要觀念
在一開始規劃狀態機時,可能會搞混一件事,就是:
「狀態(State) ≠ 資料(Data)」
這觀念非常重要,因為一旦沒在一開始就搞懂,後續流程設計上就容易混亂,甚至讓整個狀態管理越來越難維護。我們以遊戲中的情境做舉例。
假設今天有一段資料:
playerHp = 0;
很多人在剛開始設計流程時,會直接認為:「HP 等於 0,所以玩家死亡。」但其實:
- HP 是資料(Data)
- 死亡是狀態(State)
這兩者本質上是完全不同的東西。所以正確來說應該是要先區分狀態
type PlayerState =
| "alive"
| "dead"
接著,再透過資料來判斷應該進入哪個狀態。這裡我們將狀態的條件定義為判斷 playerHp 是否大於 0。其實我們可以先把資料與狀態分開理解:
資料(Data)
- 數值
- 條件
- 資訊
狀態(State)
- 流程位置
- 階段
- 處境
而狀態機真正要管理的,其實只是流程目前走到哪裡了。並不是資料本身。
建立狀態機
其實在我的實務開發經驗中,相較於後端,前端往往是最適合建立狀態機的地方,也是最容易以可視化的方式來看到建立後成果的地方。
因為前端本身就充滿了大量狀態及流程:
- 使用者互動
- UI 切換
- 非同步流程
- 動畫控制
- API 請求狀態
這些其實都非常適合透過狀態機來設計。
幾乎所有互動式 UI 都能變成狀態機
而且當流程越複雜時,狀態機帶來的幫助就會越明顯。因為狀態機能達成:
- 流程更加清楚、可預測
- 非法或意外操作更容易被阻止
- Bug 更容易被發現
很多原本容易失控的邏輯,也會因此變得穩定許多。所以裝態機的設立也應該遵從「設立流程優先,再開始狀態機。」
我們以遊戲流程來舉例,假設今天有一個會員登入按鈕。你會先開始思考可能流程如下:
使用者點擊登入
↓
進入 loading
↓
發送登入 API
↓
等待 server 回傳
↓
驗證帳號密碼
↓ ↘
成功 失敗
↓ ↓
取得 Token 帳號密碼錯誤
↓ ↓
更新會員資料 進入 error
↓ ↓
登入成功 顯示錯誤訊息
↓ ↓
進入首頁 返回入頁面
接著開始思考流程規則,也就是狀態機,當流程被畫出來後,我們就能開始進一步思考更多狀態之間的問題:
- 哪些狀態之間可以切換、能不能跳轉?
- 哪些地方可能被重複觸發?
- 哪些地方存在非同步問題?
- 哪些流程需要等待 API?
- 登入成功後是否需要同步使用者資料?
例如:
idle → loading
這是狀態的切換再流程中是合理的。因為使用者按下登入按鈕後,需要進入登入流程,此時轉換狀態,等待 API 及 server 的反應。
但是如果出現以下狀態切換:
success → error
這在我們所假設的流程中是不合理的。因為登入成功後,我們的流程應該進入首頁,而不是突然跳到登入失敗。
如果真的出現登入失敗的條件,就代表流程中可能:
- 狀態切換有問題
- API 的發送回傳處理有問題
- Token 驗證流程可能出現異常
我們就能透過這樣檢驗來重新修正流程與狀態,先定義狀態與流程規則,我才可以在真正開始動工之前,就先發現到潛在問題。這也是狀態機最大的價值。
它不是讓你寫出更複雜的程式,而是讓我們在開發前就把流程想清楚。
總結
狀態機真正的價值,在於它能幫助我們設計出更完整、更穩定的流程。
很多人覺得狀態機只是一種單純的管理狀態的工具,但我個人更覺得它更像是一套流程設計的好方法。透過設計狀態機的思維,我們不只是管理目前的狀態,更是在規劃整個流程應該如何運作,以及哪些行為是不被允許的。
所以當我們開始學習並使用狀態機時,其實是在提升自己的流程設計能力,同時也在提高對專案的整體品質。因為在設計功能之前,我們會先去思考流程中的各種可能性,而不是等到開發過程中或上線後才發現問題。
例如:
- 定義流程,確認功能完整的執行路徑
- 定義狀態,明確區分每個階段的職責
- 限制狀態的切換(Transition),避免不合理的流程跳轉
- 思考過渡的狀態(Transition State),處理動畫、非同步請求等特殊情境
- 預先考慮例外流程與錯誤處理,降低錯誤及 Bug 發生的機率
因此,狀態機最大的收穫往往不是程式碼本身,而是讓我們養成以流程為核心的思考方式。
下次當你要開始一個新專案或是開發新功能時,習慣先停一下,在要動手前先想想:
「目前處於什麼狀態?」
「下一步可以去哪裡?」
「哪些操作不應該被允許?」
你就已經開始掌握狀態機真正的價值了。
最後,感謝大家的閱讀🙏。如果這篇內容對你有幫助,也歡迎持續關注後續的分享。那我們就下篇文章見🙋♂️。
메타데이터
- post_id
- 33f684ce1d1f
- slug
- 狀態機-state-machine-33f684ce1d1f
- url
- https://medium.com/@chacha0519/%E7%8B%80%E6%85%8B%E6%A9%9F-state-machine-33f684ce1d1f
- canonical_url
- https://medium.com/@chacha0519/%E7%8B%80%E6%85%8B%E6%A9%9F-state-machine-33f684ce1d1f
- author_url
- https://medium.com/@chacha0519
- status
- ok
- fetched_at
- 2026-07-27 09:19:11