深刻React知識點整理(一)

使用React也滿一年了,從剛剛會使用到逐漸探究其底層實現,以便學習幾招奇技淫巧從而在本身的代碼中使用,寫出高效的代碼。下面整理一些知識點,算是React看書,使用,感悟的一些總結:javascript

  1. 函數式編程
  2. React事件系統
  3. 高階組件
  4. 組件性能優化
  5. React源碼初探
  6. VirtualDOM 模型

1. 函數式編程

函數式編程是一種如何編寫程序的方法論,與之對應的就是命令式編程html

以我本身的理解,函數式編程就是以函數爲中心,將大段過程拆成一個個函數,組合嵌套使用。這個思想在JavaScript中很常見。舉個阮一峯老師的例子:java

咱們有一個數學表達式:react

(1 + 2) * 3 - 4

將上述表達式不假思索的轉換成代碼:git

const a = 1 + 2;
const b = a * 3;
const c = b - 4;

以函數式編程思想:將運算過程定義成不一樣的函數,以下:程序員

const result = substract(multiply(add(1, 2), 3), 4);

是否是感受很高端但又一臉懵逼。沒錯,函數式編程在處理大段過程當中就顯得很容易理解,可是簡單邏輯中就顯得複雜,由於封裝起來的函數須要時間去閱讀。github

對上述表達式進行變形:算法

 add(1,2).multiply(3).subtract(4);

是否是也很熟悉。函數式編程在JavaScript中應用確實很廣泛。shell

目前最當紅的Python、Ruby、Javascript,對函數式編程的支持都很強,就連老牌的面向對象的Java、面向過程的PHP,都忙不迭地加入對匿名函數的支持。愈來愈多的跡象代表,函數式編程已經再也不是學術界的最愛,開始大踏步地在業界投入實用。

也許繼"面向對象編程"以後,"函數式編程"會成爲下一個編程的主流範式(paradigm)。將來的程序員恐怕或多或少都必須懂一點。編程

這裏不作多介紹,有興趣能夠看看:

2.React事件系統

React事件與DOM事件

React 基於 Virtual DOM 實現了一個 SyntheticEvent (合成事件)層,咱們所定義的事件處理器會接收到一個 SyntheticEvent 對象的實例,它徹底符合 W3C 標準,不會存在任何 IE 標準的兼容性問題。而且與原生的瀏覽器事件同樣擁有一樣的接口,一樣支持事件的冒泡機制,咱們可使用 stopPropagation() 和 preventDefault() 來中斷它。

全部事件都自動綁定到最外層上。若是須要訪問原生事件對象,可使用 nativeEvent 屬性。

使用React的時候都知道,React有一套本身的事件系統,典型的特徵就是元素綁定事件都要使用React提供的事件接口

// in html
<button onclick="activateLasers()">
  Activate Lasers
</button>

// in React
<button onClick={activateLasers}>
  Activate Lasers
</button>

React的合成事件其實是作了一層事件委託(事件代理):

它並不會把事件處理函數直接綁定到真實的節點上,而是把全部事件綁定到結構的最外層,使用一個統一的事件監聽器,這個事件監聽器上維持了一個映射來保存全部組件內部的事件監聽和處理函數。當組件掛載或卸載時,只是在這個統一的事件監聽器上插入或刪除一些對象;當事件發生時,首先被這個統一的事件監聽器處理,而後在映射裏找到真正的事件處理函數並調用。這樣作簡化了事件處理和回收機制,效率
也有很大提高。

也就是說React使用了一個事件代理,全部事件綁定都只是事件代理保存了一個映射,事件發生的時候,調用處理函數,並無真正的使用原生事件。咱們來看一個例子:

componentDidMount () {
    document.querySelector('#testEvent').addEventListener('click', (e)=>{
        console.log('dom event');
        console.log(e);
    })
}
componentDidUnMount () {
    document.querySelector('#testEvent').removeEventListener('click');
}
handleClick (e) {
    console.log('react event');
    console.log(e);
}
render () {
    return (
        <div>
              <div onClick={::this.handleClick}>Test React Event</div>
              <div id='testEvent'>Test dom Event</div>
        </div>
    );
}

這裏有兩個div,使用React綁定事件和原生DOM事件,兩種事件綁定方法不一樣致使相同的效果,徹底不一樣的原理。

使用原生DOM綁定打印的事件就是原生的,React事件打印出來的事件:
clipboard.png

能夠看到是個Proxy對象,裏面有觸發事件的target和處理事件的handler,這就是React的合成事件。

另外若是在react中綁定原生事件,組件卸載的時候記得解除綁定,避免內存泄漏。

React的合成事件還有一個優勢在於不須要處理瀏覽器事件兼容性,方便操做。

原生事件分紅三個部分:事件捕獲,目標事件處理,事件冒泡。IE9如下不支持事件捕獲,因此React沒有實現它,僅支持事件冒泡。

有些事件React沒有實現,window.resize事件。

因此,請儘可能避免在 React 中混用合成事件和原生 DOM 事件。由於二者是不一樣的事件系統,阻止 React 事件冒泡的行爲只能用於 React 合成事件系統中,且沒辦法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行爲,卻能夠阻止 React 合成事件的傳播。

3.高階組件

高階組件是React中比較有特色的一類問題,高階組件(High Order Component)文章裏單獨進行了詳細介紹。

這裏只是補一張圖:組合式組件開發實踐
clipboard.png

4.組件性能優化

從過往的經驗與實踐中,咱們都知道影響網頁性能最大的因素是瀏覽器的重繪(reflow)和重排版(repaint)。React 背後的 Virtual DOM 就是儘量地減小瀏覽器的重繪與重排版。

關於瀏覽器重繪和重排版問題,請看我以前的文章:瀏覽器渲染頁面過程與頁面優化

這裏要介紹的就是:

  1. 多使用純函數:無依賴;相同輸入相同輸出;重複使用。
  2. PureComponent:本質上講,PureComponent就是重寫了shouldComponentUpdate,對nextPropsnextState與當前state和props作淺比較,性能上優化。
  3. Immutable:使用Immutable共享數據節點,節省渲染。
  4. key:列表渲染指定key,相同key不渲染;儘可能不要使用index當key,最好是id。
  5. react-addons-pref:插件量化性能優化效果。

對這塊有興趣的,推薦幾篇文章:

5.React源碼初探

React項目目錄構成以下圖:
clipboard.png

  • addons:工具方法插件:PureRenderMixinCSSTransitionGrouoFragmentLinkedStateMixin
  • isomorphic:包含一系列同構方法。
  • shared:公用方法和經常使用方法。
  • test:測試方法。
  • core/tests:邊界錯誤的測試用例。
  • renderers:React的核心代碼,包含大部分功能實現,所以進行單獨分析。

renderers包包含內容:
clipboard.png

  • dom:包含client,server和shared。

    • client:包含DOM操做方法(findDOMNode,setInnerHTML,setTextContent等)以及事件方法。這裏的事件方法主要是一些非底層的實用性事件方法,
      如事件監聽(ReactEventListener)、經常使用事件方法(TapEventPlugin、EnterLeaveEventPlugin)以及一些合成事件(SyntheticEvents
      等)。
    • server:主要包含服務端渲染的實現和方法(如 ReactServerRendering、ReactServerRenderingTransaction
      等)。
    • shared:包含文本組件(ReactDOMTextComponent)、標籤組件(ReactDOMComponent)、
      DOM 屬性操做(DOMProperty、DOMPropertyOperations)、CSS 屬性操做(CSSProperty、
      CSSPropertyOperations)等。
  • shared:包含event和reconciler。

    • event:包含一些更爲底層的事件方法,如事件插件中心(EventPluginHub)、事件註冊
      (EventPluginRegistry)、事件傳播(EventPropagators)以及一些事件通用方法。
      React 自定義了一套通用事件的插件系統,該系統包含事件監聽器、事件發射器、事
      件插件中心、點擊事件、進/出事件、簡單事件、合成事件以及一些事件方法。
    • reconciler:稱爲協調器,它是最爲核心的部分,包含 React 中自定義組件的實現
      (ReactCompositeComponent)、組件生命週期機制、setState 機制(ReactUpdates、
      ReactUpdateQueue)、DOM diff 算法(ReactMultiChild)等重要的特性方法。

clipboard.png

這裏簡單介紹React目錄構成以及每塊的功能,大體瞭解,須要的時候找到對應位置深刻研究。

React 也可以實現 Virtual DOM 的批處理更新,當操做 Virtual DOM 時, 不會立刻生成真實的DOM,而是會將一個事件循環(event loop)內的兩次數據更新進行合併,這樣就使得 React 可以在事件循環的結束以前徹底不用操做真實的 DOM。

6.VirtualDOM 模型

VirtualDOM是React的一個核心,也是React一個著名的特色,以前我有篇文章對此有過簡單的介紹,以及如何簡單實現根據VirtualDOM渲染頁面:React學習報告,能夠作基本入門查看。

VirtualDOM與真實DOM的關係很簡單:

  • 真實DOM能夠理解爲是xml格式存儲DOM,VirtualDOM能夠理解爲json格式的存儲DOM。
  • 只須要存儲節點的關鍵信息:類型,id,class,屬性,style,事件,嵌套關係等便可,按照必定的轉換規則將json轉成DOM。
  • 流程關係:jsx語法->識別jsx語法生成VirtualDOM樹->根據渲染規則生成真實DOM->HTML。
Virtual DOM中的節點成爲ReactNode,分紅ReactELement,ReactFragment,ReactText。ReactElement又分紅ReactComponentElemnt和ReactDOMElement。

下面是 ReactNode 中不一樣類型節點所須要的基礎元素:

type ReactNode = ReactElement | ReactFragment | ReactText;
type ReactElement = ReactComponentElement | ReactDOMElement;
type ReactDOMElement = {
 type : string,
 props : {
 children : ReactNodeList,
 className : string,
 etc.
 },
 key : string | boolean | number | null,
 ref : string | null
};
type ReactComponentElement<TProps> = {
 type : ReactClass<TProps>,
 props : TProps,
 key : string | boolean | number | null,
 ref : string | null
};
type ReactFragment = Array<ReactNode | ReactEmpty>;
type ReactNodeList = ReactNode | ReactEmpty;
type ReactText = string | number;
type ReactEmpty = null | undefined | boolean;

這裏以DOM標籤(ReactDOMComponent)爲例,介紹VirtualDOM模型如何建立節點:

屬性更新

當執行 mountComponent 方法時,ReactDOMComponent 首先會生成標記和標籤,經過 this.createOpenTagMarkupAndPutListeners(transaction) 來處理 DOM 節點的屬性和事件。
  • 若是節點綁定了事件,則針對當前的節點添加代理,調用enqueuePutListener(this,propKey, propValue, transaction)
  • 存在樣式的話,樣式合併Object.assign({}, props.style),而後經過CSSPropertyOperations.createMarkupForStyles(propValue, this)建立樣式。
  • 經過DOMPropertyOperations.createMarkupForProperty(propKey, propValue)建立屬性。
  • 經過DOMPropertyOperations.createMarkupForID(this._domID)建立惟一標識。
其實,早有開發者向 React 官方提過問題,建議去掉這個雞肋的屬性標識(data-reactid)這終於在 React 15.0版本上實現了。據官方宣稱,去除 data-reactid 使得 React 性能有了 10% 的提高。

更新子節點

當執行 mountComponent 方法時,ReactDOMComponent 會經過 this._createContentMarkup(transaction, props, context) 來處理 DOM 節點的內容。

先是刪除不須要的子節點和內容。若是舊節點存在,而新節點不存在,說明當前節點在更新後被刪除,此時執行方法 this.updateChildren(null, transaction, context);若是舊的內容存在,而新的內容不存在,說明當前內容在更新後被刪除,此時執行方法 this.updateTextContent('')。

再是更新子節點和內容。若是新子節點存在,則更新其子節點,此時執行方法 this.updateChildren(nextChildren,transaction, context);若是新的內容存在,則更新內容,此時執行方法 this.updateTextContent('' + nextContent)

當卸載組件時,ReactDOMComponent 會進行一系列的操做,如卸載子節點、清除事件監聽、清空標識等。
clipboard.png

相關文章
相關標籤/搜索