這是一篇給小白的,極為通俗易懂的個(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)大佬們指教。感謝。