圖文示例講解useState與useReducer性能區別
目錄
- 引言
- 一個嚴重的bug
- bug是如何產生的
- v18之后的改變
- 總結
引言
稍微深入了解過useState
的同學都知道 —— useState
其實是預置了reducer
的useReducer
。具體來講,他預置的reducer
實現如下:
function basicStateReducer(state, action) { // $FlowFixMe: Flow doesn"t like mixed types return typeof action === "function" ? action(state) : action;}
那按理來說,useState
與useReducer
性能應該完全一致才對。但實際上,他們的性能并不一樣。本文就來聊聊他們的細微差別。
一個嚴重的bug
在v18
之前,特定場景下,useReducer
存在一個嚴重的bug
。假設我們要掛載如下App
組件:
function App() { const [disabled, setDisabled] = React.useState(false); return ( <> <button onClick={() => setDisabled((prev) => !prev)}>Disable</button> <div>{`Disabled? ${disabled}`}</div> <CounterReducer disabled={disabled} /> </> );}
通過點擊按鈕,可以切換disabled
狀態,并將disabled
作為props
傳遞給CounterReducer
組件。
CounterReducer
組件的實現如下:
function CounterReducer({ disabled }) { const [count, dispatch] = useReducer((state) => { if (disabled) { return state; } return state + 1; }, 0); return ( <> <button onClick={dispatch}>reducer + 1</button> <div>{`Count ${count}`}</div> </> );}
count
狀態初始為0,當disabled props
為true
時,點擊reducer + 1按鈕后count
不會變化。
當disabled props
為false
時,點擊reducer + 1按鈕后count
會加1。
現在問題來了,當disabled props
為true
時(此時count
為0),我們點擊reducer + 1按鈕5次,然后再點擊Disable按鈕(disabled props
會變為false
),此時count
為多少呢?
按照代碼邏輯,改變disabled
對count
不會造成影響,所以他應該保持原始狀態不變(即為0)。
但在v18
之前,他會變成5。
但是,如果我們用useState
實現同樣邏輯的useReducer
:
function CounterState({ disabled }) { const [count, dispatch] = useState(0); function dispatchAction() { dispatch((state) => { if (disabled) {return state; } return state + 1; }); } return ( <> <button onClick={dispatchAction}>state + 1</button> <div>{`Count ${count}`}</div> </> );}
就能取得符合預期的效果。
所以說,useReducer
的實現在特殊場景下是有bug
的(v18之前)。
bug是如何產生的
產生這個bug
的原因在于React
內部的一種被稱為eager state
的性能優化策略。
簡單的說,對于類似如下這樣的,即使多次觸發更新,但狀態的最終結果不變的情況(在如下例子中count
始終為0):
function App() { const [count, dispatch] = useState(0); return <button onClick={() => dispatch(0)}>點擊</button>;}
App
組件是沒有必要render
的。這就省去了render
的性能開銷。
要命中eager state
,有個嚴格的前提 —— 狀態更新前后不變。
我們知道,React
中有兩種更新狀態的方式:
- 傳遞新的狀態
// 定義狀態const [count, dispatch] = useState(0);// 更新狀態dispatch(100)
- 傳遞更新狀態的函數
// 定義狀態const [count, dispatch] = useState(0);// 更新狀態dispatch(oldState => oldState + 100)
那么,對于方式1,要保證狀態不變很簡單,只需要全等比較變化前后的狀態,如果他們一致就能進入eager state
策略。
對于方式2,就略微復雜點,需要同時滿足2個條件:
- 狀態更新函數本身不變
- 通過狀態更新函數計算出的新狀態也不變
比如,下述代碼就同時滿足2個條件,但如果將change
放到App
內就不滿足條件1(App
組件每次render
時都會創建新的change
函數):
// 狀態更新函數本身不變function change(oldState) { // 新狀態也不變 return oldState;}function App() { const [count, dispatch] = useState(0); // 狀態更新函數每次render都會變化 // function change(oldState) { // 新狀態不變 // return oldState; // } return <button onClick={() => dispatch(change)}>點擊</button>;}
類似的情況,在useState
的實現中,雖然他是預置了reducer
的useReducer
,但他預置的reducer
的引用是不變的,所以用他實現的文章開篇的例子可以命中優化策略。
useReducer
在特定場景下的bug
就與此相關。并不是說bug
產生的原因是useReducer
一定沒命中優化策略,而是說相比于useState
,他命中優化策略很不穩定。
v18之后的改變
既然bug
來源于不穩定的性能優化策略,在沒有完美的解決方案之前,React
是如何在v18
中修復這個bug
的呢?
答案是 —— 移除useReducer
的eager state
策略。也就是說,在任何情況下,useReducer
都不再有useState
存在的這個性能優化策略了。
這就導致在特定場景下,useReducer
的性能弱于useState
。
比如在v18在線示例中,同樣的邏輯用useState
實現,不會有冗余的render
,而useReducer
會有。
總結
在考慮性能優化時,如果useState
與useReducer
都能滿足需要,或許useState
是更好的選擇。
以上就是useState與useReducer性能區別圖文示例詳解的詳細內容,更多關于useState useReducer性能區別的資料請關注其它相關文章!