使用React也滿一年了,從剛剛會使用到逐漸探究其底層實現,以便學習幾招奇技淫巧從而在本身的代碼中使用,寫出高效的代碼。下面整理一些知識點,算是React看書,使用,感悟的一些總結:javascript
函數式編程是一種如何編寫程序的方法論,與之對應的就是命令式編程。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)。將來的程序員恐怕或多或少都必須懂一點。編程
這裏不作多介紹,有興趣能夠看看:
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事件打印出來的事件:
能夠看到是個Proxy對象,裏面有觸發事件的target和處理事件的handler,這就是React的合成事件。
另外若是在react中綁定原生事件,組件卸載的時候記得解除綁定,避免內存泄漏。
React的合成事件還有一個優勢在於不須要處理瀏覽器事件兼容性,方便操做。
原生事件分紅三個部分:事件捕獲,目標事件處理,事件冒泡。IE9如下不支持事件捕獲,因此React沒有實現它,僅支持事件冒泡。有些事件React沒有實現,window.resize事件。
因此,請儘可能避免在 React 中混用合成事件和原生 DOM 事件。由於二者是不一樣的事件系統,阻止 React 事件冒泡的行爲只能用於 React 合成事件系統中,且沒辦法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行爲,卻能夠阻止 React 合成事件的傳播。
高階組件是React中比較有特色的一類問題,高階組件(High Order Component)文章裏單獨進行了詳細介紹。
這裏只是補一張圖:組合式組件開發實踐
從過往的經驗與實踐中,咱們都知道影響網頁性能最大的因素是瀏覽器的重繪(reflow)和重排版(repaint)。React 背後的 Virtual DOM 就是儘量地減小瀏覽器的重繪與重排版。
關於瀏覽器重繪和重排版問題,請看我以前的文章:瀏覽器渲染頁面過程與頁面優化
這裏要介紹的就是:
shouldComponentUpdate
,對nextProps
和nextState
與當前state和props作淺比較,性能上優化。Immutable
共享數據節點,節省渲染。對這塊有興趣的,推薦幾篇文章:
React項目目錄構成以下圖:
PureRenderMixin
、CSSTransitionGrouo
、Fragment
、LinkedStateMixin
。renderers包包含內容:
dom:包含client,server和shared。
shared:包含event和reconciler。
這裏簡單介紹React目錄構成以及每塊的功能,大體瞭解,須要的時候找到對應位置深刻研究。
React 也可以實現 Virtual DOM 的批處理更新,當操做 Virtual DOM 時, 不會立刻生成真實的DOM,而是會將一個事件循環(event loop)內的兩次數據更新進行合併,這樣就使得 React 可以在事件循環的結束以前徹底不用操做真實的 DOM。
VirtualDOM是React的一個核心,也是React一個著名的特色,以前我有篇文章對此有過簡單的介紹,以及如何簡單實現根據VirtualDOM渲染頁面:React學習報告,能夠作基本入門查看。
VirtualDOM與真實DOM的關係很簡單:
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 會進行一系列的操做,如卸載子節點、清除事件監聽、清空標識等。