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

深入淺出 SetState 原理篇

開(kāi)發(fā) 前端
我知道 setState 被設(shè)計(jì)成“異步”是為了性能,但是涉及到源碼解讀我就歇菜了;我知道如何讓它同步,但是遇到真實(shí)的代碼情況時(shí),卻不知道如何下手。

前言

想起自己(2021年) 8 月份面試時(shí),被面試官們問(wèn)了好幾個(gè) setState 的問(wèn)題,現(xiàn)在想想,雖然回答上問(wèn)題,但是了解得不深刻。我知道 setState 被設(shè)計(jì)成“異步”是為了性能,但是涉及到源碼解讀我就歇菜了;我知道如何讓它同步,但是遇到真實(shí)的代碼情況時(shí),卻不知道如何下手。說(shuō)到底,當(dāng)時(shí)是準(zhǔn)備了面經(jīng)把這些概念記下來(lái),而沒(méi)有真正理解它

在認(rèn)識(shí) setState 前,我們問(wèn)幾個(gè)常見(jiàn)問(wèn)題

  • setState 是同步還是異步?
  • 如果是異步,怎么讓它同步?
  • 為什么要這樣設(shè)計(jì)?

基本概念和使用

React 的理念之一是 UI=f(data),修改 data 即驅(qū)動(dòng) UI 變化,那么怎么修改呢?React 提供了一個(gè) API ——setState(類組件的修改方法)

官網(wǎng)介紹:

  • setState() 將對(duì)組件 state 的更新排入隊(duì)列,并通知 React 需要使用更新后的 state 重新渲染此組件及其子組件。這是用于更新用戶界面以響應(yīng)事件處理器和處理服務(wù)器數(shù)據(jù)的主要方式
  • 為了更好的感知性能,React 會(huì)延遲調(diào)用它,然后通過(guò)一次傳遞更新多個(gè)組件。React 并不會(huì)保證 state 的變更會(huì)立即生效
  • setState() 并不總是立即更新組件。它會(huì)批量推遲更新。這使得在調(diào)用 setState() 后立即讀取 this.state 成為了隱患。為了消除隱患,請(qǐng)使用 componentDidUpdate 或者 setState 的回調(diào)函數(shù)(setState(updater, callback)),這兩種方式都可以保證在應(yīng)用更新后觸發(fā)
  • 除非 shouldComponentUpdate() 返回 false,否則 setState() 將始終執(zhí)行重新渲染操作。如果可變對(duì)象被使用,且無(wú)法在 shouldComponentUpdate() 中實(shí)現(xiàn)條件渲染,那么僅在新舊狀態(tài)不一致調(diào)用 setState()可以避免不必要的重新渲染

使用方法

setState(updater, [callback])


參數(shù)一為帶有形式參數(shù)的 updater 函數(shù):

(state, props) => stateChange

// 例如
// this.setState((state, props) => {
// return {counter: state.counter + props.step};
// });


setState 的第一個(gè)參數(shù)除了接受函數(shù)外,還可以接受對(duì)象類型:

setState(stateChange[, callback])
// 例如:this.setState({count: 2})


setState 的第二個(gè)參數(shù)為可選的回調(diào)函數(shù),它將在 setState 完成合并重新渲染組件后執(zhí)行。通常,我們建議使用 componentDidUpdate 來(lái)代替此方法

setState(stateChange[, callback])
// 例如: this.setState({count: 2}, () => {console.log(this.state.count)})


與 setState 回調(diào)相比,使用 componentDidUpdate 有什么優(yōu)勢(shì)?

stackoverflow 有人問(wèn)過(guò),也有人回答過(guò):

  • 一致的邏輯
  • 批量更新
  • 什么時(shí)候 setState 會(huì)比較好? 當(dāng)外部代碼需要等待狀態(tài)更新時(shí),如 Promise

setState 的特性——批處理

如果在同一周期內(nèi)對(duì)多個(gè) setState 進(jìn)行處理,例如,在同一周期內(nèi)多次設(shè)置商品數(shù)據(jù),相當(dāng)于:

this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// ===
Object.assign(
count,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)


后調(diào)的 setState 將覆蓋同一周期內(nèi)先調(diào)用 setState 的值

  • setState(stateChange[, callback])
  • setState((state, props) => stateChange[, callback])

setState 必引發(fā)更新過(guò)程,但不一定會(huì)引發(fā) render 被執(zhí)行,因?yàn)?shouldCompomentUpdate 可以返回 false

批處理引發(fā)的問(wèn)題

問(wèn)題1:連續(xù)使用 setState,為什么不能實(shí)時(shí)改變

state.count = 0;
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// state.count === 1,不是 3


因?yàn)?this.setState 方法為會(huì)進(jìn)行批處理,后調(diào)的 setState 會(huì)覆蓋統(tǒng)一周期內(nèi)先調(diào)用的 setState 的值,如下圖所示:

state.count = 0;
this.setState({count: state.count + 2});
this.setState({count: state.count + 3});
this.setState({count: state.count + 4});
// state.count === 4


問(wèn)題2:為什么要 setState,而不是直接 this.state.xx = oo?

因?yàn)?setState 做的事情不僅僅只是修改了 this.state 的值,另外最重要的是它會(huì)觸發(fā) React 的更新機(jī)制,會(huì)進(jìn)行diff,然后將 patch 部分更新到真實(shí) dom 里

如果你直接 this.state.xx = oo 的話,state 的值確實(shí)會(huì)改,但是它不會(huì)驅(qū)動(dòng) React 重渲染。setState 能幫助我們更新視圖,引發(fā) shouldComponentUpdate、render 等一系列函數(shù)的調(diào)用。至于批處理,React 會(huì)將 setState 的效果放入隊(duì)列中,在事件結(jié)束之后產(chǎn)生一次重新渲染,為的就是把 Virtual DOM 和 DOM 樹(shù)操作降到最小,用于提高性能

當(dāng)調(diào)用 setState 后,React 的 生命周期函數(shù) 會(huì)依次順序執(zhí)行

  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

問(wèn)題3:那為什么會(huì)出現(xiàn)異步的情況呢?(為什么這么設(shè)計(jì)?)

因?yàn)樾阅軆?yōu)化。假如每次 setState 都要更新數(shù)據(jù),更新過(guò)程就要走五個(gè)生命周期,走完一輪生命周期再拿 render 函數(shù)的結(jié)果去做 diff 對(duì)比和更新真實(shí) DOM,會(huì)很耗時(shí)間。所以將每次調(diào)用都放一起做一次性處理,能降低對(duì) DOM 的操作,提高應(yīng)用性能

問(wèn)題4:那如何在表現(xiàn)出異步的函數(shù)里可以準(zhǔn)確拿到更新后的 state 呢?

通過(guò)第二個(gè)參數(shù) setState(partialState, callback) 中的 callback 拿到更新后的結(jié)果

onHandleClick() {
this.setState(
{
count: this.state.count + 1,
},
() => {
console.log("點(diǎn)擊之后的回調(diào)", this.state.count); // 最新值
}
);
}


或者可以直接給 state 傳遞函數(shù)來(lái)表現(xiàn)出同步的情況

this.setState(state => {
console.log("函數(shù)模式", state.count);
return { count: state.count + 1 };
});


執(zhí)行原理

首先先了解三種渲染模式

  • legacy 模式:ReactDOM.render(, rootNode) 。這是當(dāng)前 React app 使用的方式。當(dāng)前沒(méi)有計(jì)劃刪除本模式,但是這個(gè)模式可能不支持新功能
  • blocking 模式:
  • ReactDOM.createBlockingRoot(rootNode).render() 。目前正在實(shí)驗(yàn)中,作為遷移到 concurrent 模式的第一個(gè)步驟
  • concurrent 模式 :ReactDOM.createRoot(rootNode).render()。目前在實(shí)驗(yàn)中,未來(lái)穩(wěn)定之后,打算作為 React 的模式開(kāi)發(fā)模式。這個(gè)模式開(kāi)啟了所有的新功能 擁有不同的優(yōu)先級(jí),更新的過(guò)程可以被打斷

在 legacy 模式下,在 React 的 setState 函數(shù)實(shí)現(xiàn)中,會(huì)根據(jù)一個(gè)變量 isBatchingUpdates 判斷是直接更新 this.state 還是放到隊(duì)列中回頭再說(shuō),而 isBatchingUpdates 默認(rèn)是 false,也就表示 setState 會(huì)同步更新 this.state,但是,有一個(gè)函數(shù) batchedUpdates,這個(gè)函數(shù)會(huì)把 isBatchingUpdates 修改為 true,而當(dāng) React 在調(diào)用事件處理函數(shù)之前就會(huì)調(diào)用這個(gè) batchedUpdates,造成的后果,就是由 React 控制的事件處理過(guò)程 setState 不會(huì)同步更新 this.state

像 addEventListener 綁定的原生事件、setTimeout/setInterval 會(huì)走同步,除此之外,也就是 React 控制的事件處理 setState 會(huì)異步

而 concurrent 模式都是異步,這也是未來(lái) React 18 的默認(rèn)模式

總結(jié)

首先,我們總結(jié)下關(guān)鍵知識(shí)點(diǎn)

  • setState 不會(huì)立即改變 React 組件中 state 的值
  • setState 通過(guò)引發(fā)一次組件的更新過(guò)程來(lái)引發(fā)重新繪制
  • 多次 setState 函數(shù)調(diào)用產(chǎn)生的效果會(huì)合并(批處理)

其次,回答一下文章開(kāi)頭的問(wèn)題(第二第三問(wèn)題在文中已經(jīng)回答)

setState 是同步還是異步?

  • 代碼同步,渲染看模式 legacy模式,非原生事件、setTimeout/setInterval 的情況下為異步;addEventListener 。 綁定原生事件、setTimeout/setInterval 時(shí)會(huì)同步concurrent 模式:異步

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2021-07-20 15:20:02

FlatBuffers阿里云Java

2018-12-25 08:00:00

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2020-11-06 09:24:09

node

2021-08-10 14:10:02

Nodejs后端開(kāi)發(fā)

2012-11-30 15:37:10

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2012-05-21 10:06:26

FrameworkCocoa

2022-09-26 09:01:15

語(yǔ)言數(shù)據(jù)JavaScript

2021-08-24 06:36:02

DDD領(lǐng)域驅(qū)動(dòng)微服務(wù)

2012-05-31 09:35:43

HTML5

2012-05-30 15:17:54

HTML5

2012-05-30 14:51:09

HTML5

2012-05-31 09:19:22

HTML5

2012-05-31 10:57:06

HTML5

2012-05-30 11:11:42

HTML5

2012-05-30 13:17:46

HTML5

2012-05-30 13:49:52

HTML5
點(diǎn)贊
收藏

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