自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

UseState與UseReducer性能居然有區(qū)別?

開發(fā) 前端
UseState與UseReducer性能應(yīng)該完全一致才對(duì)。但實(shí)際上,他們的性能并不一樣。本文就來聊聊他們的細(xì)微差別。

大家好,我卡頌。

稍微深入了解過useState的同學(xué)都知道 —— useState其實(shí)是預(yù)置了reducer的useReducer。具體來講,他預(yù)置的reducer實(shí)現(xiàn)如下:

function basicStateReducer(state, action) {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}

那按理來說,useState與useReducer性能應(yīng)該完全一致才對(duì)。但實(shí)際上,他們的性能并不一樣。本文就來聊聊他們的細(xì)微差別。

一個(gè)嚴(yán)重的bug

在v18之前,特定場(chǎng)景下,useReducer存在一個(gè)嚴(yán)重的bug。假設(shè)我們要掛載如下App組件:

bug復(fù)現(xiàn)地址[1]

function App() {
const [disabled, setDisabled] = React.useState(false);
return (
<>
<button onClick={() => setDisabled((prev) => !prev)}>Disable</button>
<div>{`Disabled? ${disabled}`}</div>
<CounterReducer disabled={disabled} />
</>
);
}

通過點(diǎn)擊按鈕,可以切換disabled狀態(tài),并將disabled作為props傳遞給CounterReducer組件。

CounterReducer組件的實(shí)現(xiàn)如下:

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?狀態(tài)初始為0,當(dāng)disabled props為true?時(shí),點(diǎn)擊「reducer + 1按鈕」后count不會(huì)變化。

圖片

disabled為true時(shí),多次點(diǎn)擊后count仍顯示0

當(dāng)disabled props為false?時(shí),點(diǎn)擊「reducer + 1按鈕」后count會(huì)加1。

圖片

disabled為false時(shí),點(diǎn)擊后count加1

現(xiàn)在問題來了,當(dāng)disabled props為true?時(shí)(此時(shí)count?為0),我們點(diǎn)擊「reducer + 1按鈕」5次,然后再點(diǎn)擊「Disable按鈕」(disabled props?會(huì)變?yōu)閒alse?),此時(shí)count為多少呢?

按照代碼邏輯,改變disabled對(duì)count不會(huì)造成影響,所以他應(yīng)該保持原始狀態(tài)不變(即為0)。

圖片

但在v18之前,他會(huì)變成5。

圖片

但是,如果我們用useState實(shí)現(xiàn)同樣邏輯的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>
</>
);
}

就能取得符合預(yù)期的效果。

所以說,useReducer的實(shí)現(xiàn)在特殊場(chǎng)景下是有bug的(v18之前)。

bug是如何產(chǎn)生的

產(chǎn)生這個(gè)bug的原因在于React內(nèi)部的一種被稱為eager state的性能優(yōu)化策略。

簡(jiǎn)單的說,對(duì)于類似如下這樣的,即使多次觸發(fā)更新,但狀態(tài)的最終結(jié)果不變的情況(在如下例子中??count??始終為0):

function App() {
const [count, dispatch] = useState(0);
return <button onClick={() => dispatch(0)}>點(diǎn)擊</button>;
}

App組件是沒有必要render的。這就省去了render的性能開銷。

要命中eager state,有個(gè)嚴(yán)格的前提 —— 狀態(tài)更新前后不變。

我們知道,React中有兩種更新狀態(tài)的方式:

  1. 傳遞新的狀態(tài)。
// 定義狀態(tài)
const [count, dispatch] = useState(0);

// 更新狀態(tài)
dispatch(100)
  1. 傳遞更新狀態(tài)的函數(shù)。
// 定義狀態(tài)
const [count, dispatch] = useState(0);

// 更新狀態(tài)
dispatch(oldState => oldState + 100)

那么,對(duì)于方式1,要保證狀態(tài)不變很簡(jiǎn)單,只需要全等比較變化前后的狀態(tài),如果他們一致就能進(jìn)入eager state策略。

對(duì)于方式2,就略微復(fù)雜點(diǎn),需要同時(shí)滿足2個(gè)條件:

  1. 「狀態(tài)更新函數(shù)」本身不變。
  2. 通過「狀態(tài)更新函數(shù)」計(jì)算出的新狀態(tài)也不變。

比如,下述代碼就同時(shí)滿足2個(gè)條件,但如果將change放到App內(nèi)就不滿足條件1(App組件每次render時(shí)都會(huì)創(chuàng)建新的change函數(shù)):

// 狀態(tài)更新函數(shù)本身不變
function change(oldState) {
// 新狀態(tài)也不變
return oldState;
}

function App() {
const [count, dispatch] = useState(0);

// 狀態(tài)更新函數(shù)每次render都會(huì)變化
// function change(oldState) {
// 新狀態(tài)不變
// return oldState;
// }

return <button onClick={() => dispatch(change)}>點(diǎn)擊</button>;
}

類似的情況,在useState的實(shí)現(xiàn)中,雖然他是預(yù)置了reducer的useReducer,但他預(yù)置的reducer的引用是不變的,所以用他實(shí)現(xiàn)的文章開篇的例子可以命中優(yōu)化策略。

useReducer在特定場(chǎng)景下的bug就與此相關(guān)。并不是說bug產(chǎn)生的原因是useReducer一定沒命中優(yōu)化策略,而是說相比于useState,他命中優(yōu)化策略很不穩(wěn)定。

v18之后的改變

既然bug?來源于不穩(wěn)定的性能優(yōu)化策略,在沒有完美的解決方案之前,React?是如何在v18?中修復(fù)這個(gè)bug的呢?

答案是 —— 移除useReducer?的eager state?策略。也就是說,在任何情況下,useReducer?都不再有useState存在的這個(gè)性能優(yōu)化策略了。

這就導(dǎo)致在特定場(chǎng)景下,useReducer?的性能弱于useState。

比如在這個(gè)v18在線示例[2]中,同樣的邏輯用useState?實(shí)現(xiàn),不會(huì)有冗余的render?,而useReducer會(huì)有。

總結(jié)

在考慮性能優(yōu)化時(shí),如果useState與useReducer都能滿足需要,或許useState是更好的選擇。

參考資料

[1]bug復(fù)現(xiàn)地址:https://codesandbox.io/s/vigorous-dhawan-mqv463。

[2]v18在線示例:https://codesandbox.io/s/blazing-cdn-pzcpz6?file=/src/App.js:509-519。

責(zé)任編輯:姜華 來源: 魔術(shù)師卡頌
相關(guān)推薦

2015-07-30 09:20:26

微軟Android Lau

2022-03-18 14:09:52

ReactJavaScript

2019-08-09 15:07:33

TomcatJaegerSpringBoot

2022-03-29 20:10:27

React狀態(tài)管理

2020-08-17 09:22:30

字符串子串對(duì)象

2021-08-03 22:26:46

Go函數(shù)分頁

2024-03-18 09:24:12

RocketMQ消息模型分布式

2020-12-17 10:23:41

死鎖LinuxLockdep

2025-02-28 09:30:00

?DeepSeekDeepGEMMAI

2022-01-04 09:24:32

Python Excel 表格

2023-05-25 10:03:40

2024-06-14 10:26:30

2021-06-25 10:04:47

DevOpsDevSecOps開發(fā)

2021-07-12 10:18:35

互聯(lián)網(wǎng)數(shù)據(jù)代碼

2019-02-12 11:07:49

2021-10-08 18:33:05

微信后臺(tái)移動(dòng)應(yīng)用

2020-12-22 13:46:48

APISKD

2024-01-09 16:14:39

RustGo切片

2020-09-14 09:01:43

VMware vSANSAN網(wǎng)絡(luò)虛擬化

2011-08-08 14:09:55

dhcpbootp
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)