【React】每個(gè)人都可以理解的合成事件與setState機(jī)制

這是一篇給小白的,極為通俗易懂的個(gè)人理解總結(jié)。

當(dāng)年初學(xué) react,學(xué)習(xí) setState 機(jī)制,總是被一堆源碼中的函數(shù)名搞的一頭霧水。

這里沒(méi)有任何源碼,只有經(jīng)過(guò)個(gè)人理解,總結(jié)出來(lái)的簡(jiǎn)易流程。

希望能夠幫助到,和曾經(jīng)的自己(現(xiàn)在也差不多),一樣想要精進(jìn)、卻舉步維艱的你們~


我們從一道常見(jiàn)的面試題入手:

react 的合成事件,是如何映射到真實(shí) dom 上的?


我們以一個(gè)簡(jiǎn)版的例子,看一下從用戶(hù)點(diǎn)擊動(dòng)作開(kāi)始,都發(fā)生了什么~


定義一個(gè)container:App

import React, { Component } from 'react'

class App extends Component {

? constructor(props) {

? ? super(props)

? ? this.state = {

? ? ? count: 0,

? ? }

? ? this.clickHandler = this.clickHandler.bind(this)

? }

? clickHandler() {

? ? console.log('count1:', this.state.count)

? ? this.setState({

? ? ? count: 1,

? ? })

? ? console.log('count2:', this.state.count)

? ? this.setState({

? ? ? count: 2,

? ? })

? ? console.log('count3:', this.state.count)

? }

? render() {

? ? console.log('count render', this.state.count)

? ? return <button onClick={this.clickHandler}>點(diǎn)我更新count</button>

? }

}

export default App


旅程開(kāi)始~


1.前言:元素掛載

點(diǎn)擊元素前,得先有元素。so,先簡(jiǎn)單看一下元素掛載過(guò)程~

ReactDOM.render() 方法,解析 jsx 生成 virtual dom tree(React16中,又由 virtual dom tree 生成 fiber tree),最終將真實(shí)的節(jié)點(diǎn)渲染到頁(yè)面上。

在這個(gè)過(guò)程中,react 內(nèi)部,會(huì)在每個(gè)真實(shí)的 dom 元素上,偷偷地添加一些屬性,用來(lái)配合它搞一些事情。

其中,除了root根節(jié)點(diǎn),其他所有元素,都被添加了這么個(gè)屬性:__reactEventHandlers$*******

這里,我們直接讀取了 button 元素的這個(gè)屬性。

似乎有點(diǎn)劇透了,咳咳~? ┓(?′?`?)┏

下面,進(jìn)入正題??


2.事件觸發(fā)

react 基于事件冒泡,統(tǒng)一在 document 上插入了原生的事件監(jiān)聽(tīng)方法,用于捕獲頁(yè)面任意位置的用戶(hù)操作。

【注】

經(jīng)實(shí)測(cè),這里給?document 插入的原生事件監(jiān)聽(tīng),取決于子元素設(shè)置了什么合成事件監(jiān)聽(tīng)。

每個(gè)合成事件都有對(duì)應(yīng)的原生事件,以此給 document 添加上需要的原生事件監(jiān)聽(tīng)函數(shù)。


當(dāng)用戶(hù)點(diǎn)擊 button,click 事件順著 dom 樹(shù)結(jié)構(gòu)冒泡到 document 上,document 上的原生 onclick 事件響應(yīng)函數(shù)被觸發(fā)。

這個(gè)響應(yīng)函數(shù)做了什么事呢?

大概是這樣:

function documentClickHandler(e) {

? ? // 獲取真正觸發(fā)點(diǎn)擊事件的元素節(jié)點(diǎn)

? ? const target = e.target

? ? // 執(zhí)行元素節(jié)點(diǎn)上注冊(cè)的合成事件響應(yīng)函數(shù)

? ? target.__reactEventHandlers$*******.onClick()

}

ok,事情的本質(zhì),其實(shí)就是這么簡(jiǎn)單。

到這里,那個(gè)面試題的答案,已經(jīng)很清晰了。

但是整個(gè)流程并沒(méi)有結(jié)束,我們繼續(xù),看看 setState 這個(gè)小家伙,一會(huì)兒同步、一會(huì)兒異步,到底是在干啥。


3.執(zhí)行合成事件響應(yīng)函數(shù):clickHandler

?clickHandler() {

? ? console.log('count1:', this.state.count)

? ? this.setState({

? ? ? count: 1,

? ? })

? ? console.log('count2:', this.state.count)

? ? this.setState({

? ? ? count: 2,

? ? })

? ? console.log('count3:', this.state.count)

? }

這里,連續(xù)調(diào)用了兩次 setState。但是,打印出的 count1、count2、count3,全部為 0。

這就是典型的,所謂 setState 的異步現(xiàn)象:調(diào)用 setState 后,state 的值并沒(méi)有立即更新。


4.合成事件中的 setState

以下代碼,由個(gè)人對(duì)這個(gè)流程的簡(jiǎn)化理解而來(lái),全部是偽代碼。并非由源碼精簡(jiǎn)而來(lái)。

這里的簡(jiǎn)單例子,只為簡(jiǎn)單說(shuō)明整體邏輯流程。

如有理解偏差,煩請(qǐng)指正。

let flag = false // 相當(dāng)于源碼的?isBatchingUpdates

function documentClickHandler(e) {

? ? // 獲取真正觸發(fā)點(diǎn)擊事件的元素節(jié)點(diǎn)

????const target = e.target

? ? // 設(shè)置標(biāo)記

? ??flag = true

? ? // 執(zhí)行元素節(jié)點(diǎn)上注冊(cè)的合成事件響應(yīng)函數(shù)

????target.__reactEventHandlers$*******.onClick()

? ? // 置回標(biāo)記

? ? flag = false

? ? // 觸發(fā) setState,批量更新在?onClick 中緩存的那些 state

? ? setState()

}


const arr = [ ]? ?// 相當(dāng)于源碼的?dirtyComponents


// 開(kāi)發(fā)者調(diào)用的setState,也是它??

function setState(state) {

? ? // 如果標(biāo)記是 true,就先不做更新。緩存當(dāng)前 state,等到 flag 為 false 的時(shí)候,做批量更新

? ? if (flag) {

? ??????arr.push(state)

????} else {

? ? ? ? // 這里,沒(méi)有傳 state,就說(shuō)明是合成事件執(zhí)行完,調(diào)用進(jìn)來(lái)的。批量更新緩存的那些 state

? ? ? ? if (!state) {

? ? ? ? ? ?? let stateObj = {}

? ? ? ? ? ? ??arr.forEach(state => {

?????????????????????stateObj = Object.assign(stateObj, state)

? ? ? ? ? ? ? })

??? ????????// 真正執(zhí)行 setState(我臆想的方法。??傊畱?yīng)該會(huì)有個(gè)類(lèi)似的東西。。)

? ??????????doSetState(stateObj)

????????} else {? ? ? ? ??

? ? ? ? ? ? doSetState(state)

????????}

????}

}

【注】

react 生命周期中的 setState,同理。

比如 componentDidMount 中連續(xù)調(diào)用了兩次 setState,在組件掛載過(guò)程中,需要執(zhí)行?componentDidMount 這個(gè)生命周期時(shí):

doRender() {

? ? // 做各種掛載需要的邏輯

? ? // ......? ??

? ? // 掛載完成,執(zhí)行生命周期函數(shù)。和合成事件的執(zhí)行同理,用 flag 標(biāo)記,告訴 setState 走批量更新

? ? flag = true

????componentDidMount()? ??

? ? flag = false

}


ok,旅程結(jié)束~

如有理解偏差,請(qǐng)大佬們指教。感謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容