新手學(xué)習(xí)react迷惑的點(diǎn)(二)

沒(méi)看第一篇的朋友可以移步先去看第一篇:新手學(xué)習(xí) react 迷惑的點(diǎn)(一)

創(chuàng)新互聯(lián)公司于2013年開(kāi)始,先為渾江等服務(wù)建站,渾江等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為渾江企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

第一篇反響也還不錯(cuò),很多新手都覺(jué)得很有幫助,解答了他們很久以來(lái)的疑惑,其實(shí)第一篇里面的還算基礎(chǔ)的,主要是 ES6 語(yǔ)法和 JSX 沒(méi)有深刻理解。

這第二篇稍微要難一點(diǎn),有的需要了解 React 的原理才能搞明白的,不過(guò)你放心,我都用了最簡(jiǎn)單最簡(jiǎn)單的語(yǔ)言,即使你是個(gè)新手,如果產(chǎn)生了這些疑問(wèn),你也能看懂。

下面開(kāi)始吧!

為什么調(diào)用方法要 bind this

前提知識(shí):?深刻的理解 JavaScript 中的 this

相信剛寫 React 的時(shí)候,很多朋友可能會(huì)寫類似這樣的代碼:

class?Foo?extends?React.Component?{
?handleClick?()?{
?this.setState({?xxx:?aaa?})
?}
?render()?{
?return?(
?<button?onClick={this.handleClick}>
?Click?me
?</button>
?)
?}
}
復(fù)制代碼

發(fā)現(xiàn)會(huì)報(bào) this 是 undefined 的錯(cuò),然后可能對(duì)事件處理比較疑惑,然后去看官網(wǎng)的事件處理有下面一段話:

你必須謹(jǐn)慎對(duì)待 JSX 回調(diào)函數(shù)中的 this,在 JavaScript 中,class 的方法默認(rèn)不會(huì)綁定this。如果你忘記綁定 this.handleClick 并把它傳入了 onClick,當(dāng)你調(diào)用這個(gè)函數(shù)的時(shí)候 this 的值為 undefined。這并不是 React 特有的行為;這其實(shí)與 JavaScript 函數(shù)工作原理有關(guān)。通常情況下,如果你沒(méi)有在方法后面添加 (),例如 onClick={this.handleClick},你應(yīng)該為這個(gè)方法綁定 this。

然后你看了官網(wǎng)的例子和建議之后,知道需要為事件處理函數(shù)綁定 this就能解決,想下面這樣:

class?Foo?extends?React.Component?{
?handleClick?()?{
?this.setState({?xxx:?aaa?})
?}
?render()?{
?return?(
?<button?onClick={this.handleClick.bind(this)}>
?Click?me
?</button>
?)
?}
}
復(fù)制代碼

但是可能你沒(méi)有去思考過(guò)為什么需要 bind this?如果你不能理解的話,還是 js 的基礎(chǔ)沒(méi)有打好。

React 是如何處理事件的?

咱們先來(lái)了解一下 React 是如何處理事件的。

React 的事件是合成事件, 內(nèi)部原理非常復(fù)雜,我這里只把關(guān)鍵性,可以用來(lái)解答這個(gè)問(wèn)題的原理部分進(jìn)行介紹即可(后面應(yīng)該會(huì)寫一篇 react 的事件原理,敬請(qǐng)期待)。

上篇文章已經(jīng)說(shuō)過(guò),jsx 實(shí)際上是 React.createElement(component, props, …children) 函數(shù)提供的語(yǔ)法糖,那么這段 jsx 代碼:

?<button?onClick={this.handleClick}>
?Click?me
?</button>
復(fù)制代碼

會(huì)被轉(zhuǎn)化為:

React.createElement("button",?{
?onClick:?this.handleClick
},?"Click?me")
復(fù)制代碼

了解了上面的,然后簡(jiǎn)單的理解 react 如何處理事件的,React 在組件加載(mount)和更新(update)時(shí),將事件通過(guò) addEventListener 統(tǒng)一注冊(cè)到 document 上,然后會(huì)有一個(gè)事件池存儲(chǔ)了所有的事件,當(dāng)事件觸發(fā)的時(shí)候,通過(guò) dispatchEvent 進(jìn)行事件分發(fā)。

所以你可以簡(jiǎn)單的理解為,最終 this.handleClick 會(huì)作為一個(gè)回調(diào)函數(shù)調(diào)用。

理解了這個(gè),然后再來(lái)看看回調(diào)函數(shù)為什么就會(huì)丟失 this。

this 簡(jiǎn)單回顧

在函數(shù)內(nèi)部,this的值取決于函數(shù)被調(diào)用的方式。

如果你不能理解上面那句話,那么你可能需要停下來(lái)閱讀文章,去查一下相關(guān)資料,否則你可能看不懂下面的,如果你懶的話,就看為你準(zhǔn)備好的 MDN 吧。

通過(guò)上面對(duì)事件處理的介紹,來(lái)模擬一下在類組件的 render 函數(shù)中, 有點(diǎn)類似于做了這樣的操作:

class?Foo?{
?sayThis?()?{
?console.log(this);?//?這里的?`this`?指向誰(shuí)?
?}
?
?exec?(cb)?{
?cb();
?}
?
?render?()?{
?this.exec(this.sayThis);
?}
}
var?foo?=?new?Foo();
foo.render();?//?輸出結(jié)果是什么?
復(fù)制代碼

你會(huì)發(fā)現(xiàn)最終結(jié)果輸出的是 undefined,如果你不理解為什么輸出的是 undefined,那么還是上面說(shuō)的,需要去深刻的理解 this 的原理。如果你能理解輸出的是 undefined,那么我覺(jué)得你就可以理解為什么需要 bind this 了。

那么你可能會(huì)問(wèn):**為什么React沒(méi)有自動(dòng)的把 bind 集成到 render 方法中呢?**在 exec 調(diào)用回調(diào)的時(shí)候綁定進(jìn)去,像這樣:

class?Foo?{
?sayThis?()?{
?console.log(this);?//?這里的?`this`?指向誰(shuí)?
?}
?exec?(cb)?{
?cb.bind(this)();
?}
?render?()?{
?this.exec(this.sayThis);
?}
}
var?foo?=?new?Foo();
foo.render();?//?輸出結(jié)果是什么?
復(fù)制代碼

因?yàn)?render 多次調(diào)用每次都要 bind 會(huì)影響性能,所以官方建議你自己在 constructor 中手動(dòng) bind 達(dá)到性能優(yōu)化。

四種事件處理對(duì)比

對(duì)于事件處理的寫法也有好幾種,咱們來(lái)進(jìn)行對(duì)比一下:

1. 直接 bind this 型

就是像文章開(kāi)始的那樣,直接在事件那里 bind this

class?Foo?extends?React.Component?{
?handleClick?()?{
?this.setState({?xxx:?aaa?})
?}
?render()?{
?return?(
?<button?onClick={this.handleClick.bind(this)}>
?Click?me
?</button>
?)
?}
}
復(fù)制代碼

優(yōu)點(diǎn):寫起來(lái)順手,一口氣就能把這個(gè)邏輯寫完,不用移動(dòng)光標(biāo)到其他地方。

缺點(diǎn):性能不太好,這種方式跟 react 內(nèi)部幫你 bind 一樣的,每次 render 都會(huì)進(jìn)行 bind,而且如果有兩個(gè)元素的事件處理函數(shù)式同一個(gè),也還是要進(jìn)行 bind,這樣會(huì)多寫點(diǎn)代碼,而且進(jìn)行兩次 bind,性能不是太好。(其實(shí)這點(diǎn)性能往往不會(huì)是性能瓶頸的地方,如果你覺(jué)得順手,這樣寫完全沒(méi)問(wèn)題)

2. constuctor 手動(dòng) bind 型

class?Foo?extends?React.Component?{
?constuctor(props)?{
?super(props)
?this.handleClick?=?this.handleClick.bind(this)
?}
?handleClick?()?{
?this.setState({?xxx:?aaa?})
?}
?render()?{
?return?(
?<button?onClick={this.handleClick}>
?Click?me
?</button>
?)
?}
}
復(fù)制代碼

優(yōu)點(diǎn):?相比于第一種性能更好,因?yàn)闃?gòu)造函數(shù)只執(zhí)行一次,那么只會(huì) bind 一次,而且如果有多個(gè)元素都需要調(diào)用這個(gè)函數(shù),也不需要重復(fù) bind,基本上解決了第一種的兩個(gè)缺點(diǎn)。

缺點(diǎn):?沒(méi)有明顯缺點(diǎn),硬要說(shuō)的話就是太丑了,然后不順手(我覺(jué)得丑,你覺(jué)得不丑就這么寫就行了)。

3. 箭頭函數(shù)型

class?Foo?extends?React.Component?{
?handleClick?()?{
?this.setState({?xxx:?aaa?})
?}
?render()?{
?return?(
?<button?onClick={(e)?=>?this.handleClick(e)}>
?Click?me
?</button>
?)
?}
}
復(fù)制代碼

優(yōu)點(diǎn):?順手,好看。

缺點(diǎn):?每次 render 都會(huì)重復(fù)創(chuàng)建函數(shù),性能會(huì)差一點(diǎn)。

4. public class fields 型

這種 class fields還處于實(shí)驗(yàn)階段,據(jù)我所知目前還沒(méi)有被納入標(biāo)準(zhǔn),具體可見(jiàn)這里。

class?Foo?extends?React.Component?{
?handleClick?=?()?=>?{
?this.setState({?xxx:?aaa?})
?}
?render()?{
?return?(
?<button?onClick={this.handleClick}>
?Click?me
?</button>
?)
?}
}
復(fù)制代碼

優(yōu)點(diǎn):?好看,性能好。

缺點(diǎn):?沒(méi)有明顯缺點(diǎn),如果硬要說(shuō)可能就是要多裝一個(gè) babel 插件來(lái)支持這種語(yǔ)法。

總結(jié)

我平時(shí)用的就這四種寫法,我這邊從代碼的美觀性、性能以及是否順手方便對(duì)各種寫法做了簡(jiǎn)單的對(duì)比。其實(shí)每種方法在項(xiàng)目里用都是沒(méi)什么問(wèn)題的,性能方面基本上可以忽略,對(duì)于美觀性和順手比較主觀,所以總體來(lái)說(shuō)就是看大家的偏好咯,如果硬要推薦的話,我還是比較推薦第四種寫法,美觀而且不影響性能。

為什么要 setState,而不是直接 this.state.xx = oo

這個(gè)問(wèn)題是我們公司后端寫 React 的時(shí)候提出的問(wèn)題,為啥不能直接修改 state,要 setState 一下。我在想,從 vue 轉(zhuǎn)到 React 可能也會(huì)有這種疑問(wèn),因?yàn)?vue 修改狀態(tài)都是直接改的。

如果我們了解 setState 的原理的話,可能就能解答這個(gè)問(wèn)題了,setState 做的事情不僅僅只是修改了 this.state 的值,另外最重要的是它會(huì)觸發(fā) React 的更新機(jī)制,會(huì)進(jìn)行 diff ,然后將 patch 部分更新到真實(shí) dom 里。

如果你直接 this.state.xx == oo 的話,state 的值確實(shí)會(huì)改,但是改了不會(huì)觸發(fā) UI 的更新,那就不是數(shù)據(jù)驅(qū)動(dòng)了。

那為什么 Vue 直接修改 data 可以觸發(fā) UI 的更新呢?因?yàn)?Vue 在創(chuàng)建 UI 的時(shí)候會(huì)把這些 data 給收集起來(lái),并且在這些 data 的訪問(wèn)器屬性 setter 進(jìn)行了重寫,在這個(gè)重寫的方法里會(huì)去觸發(fā) UI 的更新。如果你想更多的了解 vue 的原理,可以去購(gòu)買染陌大佬的剖析 Vue.js 內(nèi)部運(yùn)行機(jī)制。

不明白訪問(wèn)器屬性的可以看這篇文章:深入理解JS里的對(duì)象

setState 是同步還是異步相關(guān)問(wèn)題

1. setState 是同步還是異步?

我的回答是執(zhí)行過(guò)程代碼同步的,只是合成事件和鉤子函數(shù)的調(diào)用順序在更新之前,導(dǎo)致在合成事件和鉤子函數(shù)中沒(méi)法立馬拿到更新后的值,形式了所謂的“異步”,所以表現(xiàn)出來(lái)有時(shí)是同步,有時(shí)是“異步”。

2. 何時(shí)是同步,何時(shí)是異步呢?

只在合成事件和鉤子函數(shù)中是“異步”的,在原生事件和 setTimeout/setInterval等原生 API 中都是同步的。簡(jiǎn)單的可以理解為被 React 控制的函數(shù)里面就會(huì)表現(xiàn)出“異步”,反之表現(xiàn)為同步。

3. 那為什么會(huì)出現(xiàn)異步的情況呢?

為了做性能優(yōu)化,將 state 的更新延緩到最后批量合并再去渲染對(duì)于應(yīng)用的性能優(yōu)化是有極大好處的,如果每次的狀態(tài)改變都去重新渲染真實(shí) dom,那么它將帶來(lái)巨大的性能消耗。

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

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

或者可以通過(guò)給 setState 傳遞函數(shù)來(lái)表現(xiàn)出同步的情況:

this.setState((state)?=>?{
	return?{?val:?newVal?}
})
復(fù)制代碼

5. 那表現(xiàn)出異步的原理是怎么樣的呢?

直接講源碼肯定篇幅不夠,可以看這篇文章:你真的理解setState嗎?。

我這里還是用最簡(jiǎn)單的語(yǔ)言讓你理解:在 React 的 setState 函數(shù)實(shí)現(xiàn)中,會(huì)根據(jù) isBatchingUpdates(默認(rèn)是 false) 變量判斷是否直接更新 this.state 還是放到隊(duì)列中稍后更新。然后有一個(gè) batchedUpdate 函數(shù),可以修改 isBatchingUpdates 為 true,當(dāng) React 調(diào)用事件處理函數(shù)之前,或者生命周期函數(shù)之前就會(huì)調(diào)用 batchedUpdate 函數(shù),這樣的話,setState 就不會(huì)同步更新 this.state,而是放到更新隊(duì)列里面后續(xù)更新。

這樣你就可以理解為什么原生事件和 setTimeout/setinterval 里面調(diào)用 this.state 會(huì)同步更新了吧,因?yàn)橥ㄟ^(guò)這些函數(shù)調(diào)用的 React 沒(méi)辦法去調(diào)用 batchedUpdate 函數(shù)將 isBatchingUpdates 設(shè)置為 true,那么這個(gè)時(shí)候 setState 的時(shí)候默認(rèn)就是 false,那么就會(huì)同步更新。

最后

setState 是 React 非常重要的一個(gè)方法,值得大家好好去研究一下他的原理。

有更多視頻資料,加小可樂(lè)丫

新手學(xué)習(xí) react 迷惑的點(diǎn)(二)

新手學(xué)習(xí) react 迷惑的點(diǎn)(二)

網(wǎng)頁(yè)題目:新手學(xué)習(xí)react迷惑的點(diǎn)(二)
標(biāo)題路徑:http://www.muchs.cn/article0/jpccio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供虛擬主機(jī)、網(wǎng)站建設(shè)、全網(wǎng)營(yíng)銷推廣標(biāo)簽優(yōu)化、網(wǎng)站改版品牌網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

小程序開(kāi)發(fā)