除蟲花掉的時間可長可短,是不可控的因素,它可能佔用了好幾個小時就為了一個錯誤。當應用程式越來越大時,定位錯誤成了問題。有些操作是在某特定狀況下操作才有意義,如果有一個狀態圖成為真相的來源是不是更好?
先來看一下原始代碼:
// viewing -> editing -> viewing 
const [value, setValue] = useState('');
const [committedValue, setCommittedValue] = useState('');
const [mode, setMode] = useState('viewing');
const textEditStart = () => {
  setValue(committedValue);
  setMode('editing');
}
const textChange = (newValue) => {
  setValue(newValue);
}
const textCommit = () => {
  setCommittedValue(value);
  setMode('viewing');
}
const textCancel = () => {
  setValue(committedValue);
  setMode('viewing');
}
狀態不一致的風險
// 可能出現的錯誤情況
setMode('editing');
// 如果這裡出錯,mode 已經改變但 value 沒有載入
setValue(committedValue);
無效操作沒有保護
// viewing 狀態下誤觸 textChange
textChange('new value'); // 沒有檢查 mode,直接修改了 value
// editing 狀態下誤觸 textEditStart
textEditStart(); // 可能導致資料丟失
邏輯散落各處
除錯困難
// 出現 bug 時很難回答這些問題:
// - 在什麼狀態下發生的?
// - 允許的狀態轉換有哪些?
// - 是誰觸發了這個狀態變化?
如果我們改成這樣:
const textMachine = {
  context: { value: '', committedValue: '' },
  initial: 'viewing',
  states: {
    viewing: {
      on: {
        EDIT_START: {
          target: 'editing',
          actions: {
            value: ({ context }) => context.committedValue
          }
        }
      }
    },
    editing: {
      on: {
        TEXT_CHANGE: {
          actions: {
            value: (_, event) => event.value
          }
        },
        TEXT_COMMIT: {
          actions: {
            committedValue: ({ context }) => context.value
          },
          target: 'viewing'
        },
        TEXT_CANCEL: {
          actions: {
            value: ({ context }) => context.committedValue
          },
          target: 'viewing'
        }
      }
    }
  }
}
| 層面 | 傳統做法 | XState 狀態機 | 
|---|---|---|
| 程式碼量 | 較少,直觀簡潔 | 較多,結構化樣板代碼 | 
| 學習成本 | 低,React 原生概念 | 中等,需要理解狀態機概念 | 
| 錯誤預防 | 依賴開發者手動檢查 | 設計層面就避免無效操作 | 
| 狀態一致性 | 容易出現不一致 | 強制保證狀態一致性 | 
| 邏輯集中性 | 分散在各個函數 | 集中在狀態機定義中 | 
| 可視化 | 需要閱讀程式碼理解 | 可直接產生狀態圖 | 
| 除錯難度 | 需要追蹤多個變數 | 明確的狀態和轉換歷史 | 
| 擴展性 | 隨複雜度增加而困難 | 結構化,容易擴展新狀態 | 
const textChange = (newValue) => {
  // 每次操作都要檢查是否允許
  if (mode !== 'editing') {
    console.warn('只能在編輯模式下修改文字');
    return;
  }
  setValue(newValue);
}
editing: {
  on: {
    TEXT_CHANGE: { /* 只有在 editing 狀態才會處理 */ }
    // EDIT_START 事件會被忽略,因為沒有定義
  }
}
XState 帶來狀態管理新視角,從情境去看操作而不是在操作中檢查。
相比之下,雖然有更多的樣板代碼與 Redux 相似,但唯一差異是這些操作只會在某個情境下觸發,從根本上減少發生 bug 的可能性。
這種**「設計先行」**的思維模式,讓我們在寫程式碼之前就能:
最終實現**「預防勝於治療」**的開發理念,將原本不可控的除錯時間,轉換為可控的設計時間。
「與其花時間除錯,不如花時間設計」- 這就是 XState 狀態機帶給我們的核心價值。