複雜系統的狀態轉換

Modern user flows

State Management

with XState

一種常見的 Pattern

const App = () => {
   const { data, isLoading, isSuccess, isError, error } = useGetPostsQuery()
   if(isLoading) return <LoadingSpinner />
   if(isError) return <ErrorContentBlock message={error.message} />
   if(isSuccess) return <PostGallery posts={data || []} />
   
   return <IdleBlock />
}

How Many Flags?

State of loading Post

idle

loading

success

failure

fetch

讓我們來消滅 Flags

const App = () => {
   const { data, status, error } = useGetPostsQuery()
   if(status === 'loading') return <LoadingSpinner />
   if(status === 'failure') return <ErrorContentBlock message={error.message} />
   if(status === 'success') return <PostGallery posts={data || []} />
   
   return <IdleBlock />
}

How to change status?

Flow of loading Post

idle

loading

success

failure

fetch

FETCH

RESOLVE

REJECT

RETRY

Behind The Scenes

const useGetPostsQuery = () => {
  const [data, setData] = useState([]);
  const [error, setError] = useState();
  const [status, setStatus] = useState("idle");

  useEffect(() => {
    async function getPosts() {
      setStatus("loading");
      try {
        const res = await fetchPosts();
        setData(res);
        setStatus("success");
      } catch (err) {
        setError(err);
        setStatus("failure");
      }
    }
    
    getPosts();
  }, []);

  return { data, status, error };
};

What about retry?

Transition relies on
Promise

Flow of loading Post

idle

loading

success

failure

fetch

FETCH

RESOLVE

REJECT

RETRY

state

event

state

event

nextState = f ( previousState, event )

What we have?

What we need?

f is so called transition

Brainstorming

Finite State Machine is your savior

一個 初始狀態 (Initial State)

有限狀態機

idle

loading

success

failure

FETCH

RESOLVE

REJECT

RETRY

一組 有限數量的狀態 (States)

一組 有限數量的事件 (Events)

一組 轉移紀錄 (Transitions)

一組 有限數量的結束狀態 (Final States)

Flow of loading Post

How to develop

How to develop

  • 按下 ⇦、⇨ 可控制玩家移動
  • 按下 B 是跳躍
  • 長按 ⇩ 是臥倒 → 放開 ⇩ 是起身

先從 移動模組 開工好了...

按下 B 是跳躍

  if(input == PRESS_B){
+   if(!isJumping){
+     isJumping = true
      ...運算座標、展示跳躍圖片
+   }
  }

防止 Air Jumping

跳躍中
isJumping

  if(input == PRESS_B){
    if(!isJumping){
      isJumping = true
      ...運算座標、展示跳躍圖片
    }
- }  
+ } else if(input == PRESS_DOWN){
+   if(!isJumping){
+     ...運算座標、展示匍匐圖片
+   }
+ }
    

跳躍時不能臥倒

長按 ⇩ 是臥倒

  if(input == PRESS_B){
    if(!isJumping){
      isJumping = true
      ...運算座標、展示跳躍圖片
    }  
  } else if(input == PRESS_DOWN){
    if(!isJumping){
+     isCrawling = true
      ...運算座標、展示臥倒圖片
    }
- }
+ } else if (input == RELEASE_DOWN){
+   if(isCrawling){
+     isCrawling = false
+     ...運算座標、展示站立圖片
+   }
+ }
  

何以鑑別起身、臥倒

放開 ⇩ 是起身

臥倒中
isCrawing

  if(input == PRESS_B){
-   if(!isJumping){
+   if(!isJumping && !isCrawling){
      isJumping = true
      ...運算座標、展示跳躍圖片
    }  
  } else if(input == PRESS_DOWN){
    if(!isJumping){
      isCrawling = true
      ...運算座標、展示臥倒圖片
    }
  } else if (input == RELEASE_DOWN){
    if(isCrawling){
      isCrawling = false
      ...運算座標、展示站立圖片
    }
  }
  

臥倒不能直接跳躍

還要注意什麼?

  if(input == PRESS_B){
    if(!isJumping && !isCrawling){
      isJumping = true
      ...運算座標、展示跳躍圖片
    }  
  } else if(input == PRESS_DOWN){
    if(!isJumping){
      isCrawling = true
      ...運算座標、展示臥倒圖片
    }
  } else if (input == RELEASE_DOWN){
    if(isCrawling){
      isCrawling = false
      ...運算座標、展示站立圖片
    }
  }
  

心累......

夠複雜嗎?

心累......

夠複雜嗎?

不符合 開放封閉原則

隨著功能增加

犯錯率 上升

可讀性 下降

溝通成本 提高

除錯難度 上升

換個角度觀察

1. 獨立的狀態

2. 舊狀態 新狀態,

    有明確轉移路徑

How about 有限狀態機

Flowchart vs State Diagram

Flowchart vs State Diagram

移動模組

實作 FSM

function fetchReducer(state, event){
  switch (state) {
    case 'idle':
      switch (event.type) {
        case 'FETCH':
          return 'loading';
        default:
          return state;
      }
    case 'loading':
      switch (event.type) {
        case 'RESOLVE':
          return 'success';
        case 'REJECT':
          return 'failure';
        default:
          return state;
      }
    case 'success':
      switch (event.type) {
        default:
          return state;
      }
    case 'failure':
      switch (event.type) {
        case 'RETRY':
          return 'loading';
        default:
          return state;
      }
    default:
      return state;
  }
}

1. 有限的狀態

2. 有限的事件

3. 舊狀態 新狀態,

    有明確轉移路徑

實作 FSM

const machine = {
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      on: {
        RESOLVE: 'success',
        REJECT: 'failure'
      }
    },
    success: {
      type: 'final'
    },
    failure: {
      on: {
        RETRY: 'loading'
      }
    }
  }

1. 有限的狀態

2. 有限的事件

3. 舊狀態 新狀態,

    有明確轉移路徑

const transition = (state, event) => {
  return machine
    .states[state] // 當前的狀態
    .on[event]     // 事件後要轉移的狀態
    || state;      // or 原狀態
}
const machine = {
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      on: {
        RESOLVE: 'success',
        REJECT: 'failure'
      }
    },
    success: {
      type: 'final'
    },
    failure: {
      on: {
        RETRY: 'loading'
      }
    }
  }

XState

import { createMachine } from 'xstate'
const machine = createMachine ({
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      on: {
        RESOLVE: 'success',
        REJECT: 'failure'
      }
    },
    success: {
      type: 'final'
    },
    failure: {
      on: {
        RETRY: 'loading'
      }
    }
  })

Why XState?

State 爆炸?

State 爆炸?

State 爆炸?

State Chart

State Chart

State Chart

複雜系統的狀態轉換

Modern   user flows

State Management

with XState

?

  1. Finite State Machine (1945)

  2. State Chart (1984)

  3. W3C 的 SCXML (2005 - 2015)

State Management

Some Cool Stuff

調

UI

複雜系統的狀態轉換 Mordern user flows with XState

By 陳Ken

複雜系統的狀態轉換 Mordern user flows with XState

從狀態管理 到 狀態轉移;從 Finite state machine 到 XState

  • 399