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?
idle
loading
success
failure
fetch
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?
idle
loading
success
failure
fetch
FETCH
RESOLVE
REJECT
RETRY
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
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
一個 初始狀態 (Initial State)
有限狀態機
idle
loading
success
failure
FETCH
RESOLVE
REJECT
RETRY
一組 有限數量的狀態 (States)
一組 有限數量的事件 (Events)
一組 轉移紀錄 (Transitions)
一組 有限數量的結束狀態 (Final States)
先從 移動模組 開工好了...
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 有限狀態機
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. 舊狀態 → 新狀態,
有明確轉移路徑
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'
}
}
}
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'
}
}
})