天天看點

React源碼分析5 -- 元件通信,refs,key,ReactDOM1 元件間通信2 refs3 key4 無狀态元件5 React DOM

react源碼系列文章,請多支援:

<a href="https://www.atatech.org/articles/72905">react源碼分析1 — 元件和對象的建立(createclass,createelement)</a>

<a href="http://www.atatech.org/articles/72908">react源碼分析2 — react元件插入dom流程</a>

<a href="https://www.atatech.org/articles/73748">react源碼分析3 — react生命周期詳解</a>

<a href="https://www.atatech.org/articles/73749">react源碼分析4 — setstate機制</a>

<a href="https://www.atatech.org/articles/74571">react源碼分析5 -- 元件通信,refs,key,reactdom</a>

<a href="https://www.atatech.org/articles/74572">react源碼分析6 — react合成事件系統</a>

react規定了明确的單向資料流,利用props将資料從父元件傳遞給子元件。故我們可以利用props,讓父元件給子元件通信。故父元件向子元件通信還是很容易實作的。引申一點,父元件怎麼向孫子元件通信呢?可以利用props進行層層傳遞,使用es6的...運算符可以用很簡潔的方式把props傳遞給孫子元件。這裡我們就不舉例了。

要注意的一點是,setprops,replaceprops兩個api已經被廢棄了,react建議我們在頂層使用reactdom.reader()進行props更新。

react資料流是單向的,隻能從父元件傳遞到子元件。那麼子元件怎麼向父元件通信呢?其實仍然可以利用props。父元件利用props傳遞方法給子元件,子元件回調這個方法的同時,将資料傳遞進去,使得父元件的相關方法得到回調,這個時候就可以把資料從子元件傳遞給父元件了。看一個例子。

這個例子應該很清楚了,通過回調的方式,可以将資料從子元件傳遞給父元件。引申一下,孫子元件怎麼把資料傳遞給父元件呢?同樣可以利用props層層回調。利用es6的...運算符也可以用比較簡潔的方式完成props層層回調。

兄弟元件可以利用父元件進行中轉,将資料先由child1傳給parent,然後parent傳給child2. 這個方法顯然耦合比較嚴重,傳遞次數過多,容易引發父元件不必要的生命周期回調,甚至影響其他子元件,故強烈建議不要使用這個方式。

我們可以利用觀察者模式來解決這個問題。觀察者模式采用釋出/訂閱的方法,可以将消息發送者和接收者完美解耦。react中可以引入eventproxy子產品,利用eventproxy.trigger()方法釋出消息,eventproxy.on()方法監聽并接收消息。eventproxy我們就不展開講了。下面看一個例子

祖父元件和孫子元件通信時,我們有時候還是覺得通過props有點繁瑣了。此時可以考慮使用context全局變量。使用方法:

祖父元件中定義getchildcontext()方法,将要傳遞給孫子的資料放在其中

祖父元件中childcontexttypes申明要傳遞的資料類型

孫子元件中contexttypes申明可以接收的資料類型

孫子元件通過this.context通路祖父傳遞進來的資料。

采用全局變量的方式,容易導緻資料混亂,分不清資料是從哪兒來的,不容易控制。建議少用這種方式。

還可以利用flux和redux架構來進行元件通信,這個我們以後再專門詳細分析。

我們在getrender()傳回的jsx中,可以在标簽中加入ref屬性,然後通過refs.ref就可以通路到我們的component了,例如。

refs的用法很簡單,隻需要jsx中定義好ref屬性即可。那麼首先一個問題來了,refs這個對象在哪兒定義的呢?還記得createclass方法的constructor吧,它裡面會定義并初始化refs對象。源碼如下

從上面代碼可見,每次建立自定義元件的時候,都會初始化一個為空的refs對象。那麼第二個問題來了,ref字元串所指向的對象的引用,是什麼時候加入到refs對象中的呢?答案就在reactcompositecomponent的attachref方法中,源碼如下

attachref方法又是什麼時候被調用的呢?我們這兒就不源碼分析了。大概說下,mountcomponent中,如果element的ref屬性不為空,則會以transaction事務的方式調用attachrefs方法,而attachrefs方法中則會調用attachref方法,将子元件的引用儲存到父元件的refs對象中。

對記憶體管理有些了解的同學肯定會有疑惑,既然父元件的refs中儲存了子元件引用,那麼當子元件被unmountcomponent而銷毀時,子元件的引用仍然儲存在refs對象中,豈不是會導緻記憶體洩漏?react當然不會有這個bug了,秘密就在detachref方法中,源碼如下

代碼很簡單,delete掉ref字元串指向的成員即可。至于detachref的調用鍊,我們還得從unmountcomponent方法說起。unmountcomponent會調用detachrefs方法,而detachrefs中則會調用detachref,進而将子元素引用從refs中釋放掉,防止記憶體洩漏。也就是說在unmountcomponent時,react自動幫我們完成了子元素ref删除,防止記憶體洩漏。

當我們的子元件是一個數組時,比如類似于android中的listview,一個清單中有很多樣式一緻的項,此時給每個項加上key這個屬性就很有作用了。key可以标示目前項的唯一性。

對于數組,其内部包含長度不确定的子項。當元件state變化時,需要重新渲染元件。那麼有個問題來了,react是更新元件,還是先銷毀再建立元件呢。key就是用來解決這個問題的。如果前後兩次key不變,則隻需要更新,否則先銷毀再更新。

對于子項的key,必須是唯一不重複的。并且盡量傳不變的屬性,千萬不要傳無意義的index或者随機值。這樣才能盡量以更新的方式來重新渲染。react源碼中判斷更新方式的源碼如下

看到key這個屬性的重要性了吧。對于數組元件,我們一定要在每個子項上設定一個key,這樣可以大大提高dom diff的性能。

那為什麼數組元件之外的其他元件,不用設定key呢?因為他們的type或者在父元件中的位置不同,完全可以區分開,是以不需要key就可以完全确定是哪個元件了。

無狀态元件其實本質上就是一個函數,傳入props即可,沒有state,也沒有生命周期方法。元件本身對應的就是render方法。例子如下

無狀态元件不會建立對象,故比較省記憶體。沒有複雜的生命周期方法調用,故流程比較簡單。沒有state,也不會重複渲染。它本質上就是一個函數而已。

對于沒有狀态變化的元件,react建議我們使用無狀态元件。總之,能用無狀态元件的地方,就用無狀态元件。

react通過finddomnode()可以找到元件執行個體對應的dom節點,但需要注意的是,我們隻能在render()之後,也就是componentdidmount()和componentdidupdate()中調用。因為隻有render後,dom對象才生成了。

那為什麼componentdidmount()能通路dom呢?它不是也在mountcomponent()方法流程中嗎?這是因為react采用異步事務的方式來調用componentdidmount的,它把componentdidmount放到一個事務隊列中,隻有目前mountcomponent這個事務處理完了,才會回過頭去處理componentdidmount,故在componentdidmount中可以拿到真實的dom。這個設計得給react點贊。這一點可以從源碼來分析。

另外值得注意的是,react不建議我們碰底層的dom,因為react有一套性能比較高的dom diff方式來更新真實dom。并且容易導緻dom引用忘記釋放等記憶體洩漏問題。一句話,除非不得已,不要碰dom。

繼續閱讀