做者:朱靈子javascript
React 是一個 Facebook 和 Instagram 用來建立用戶界面的 JavaScript 庫。
創造 React 是爲了解決一個問題:構建隨着時間數據不斷變化的大規模應用程序。
本文初探react的頂層事件代理機制~java
React採用的是頂層的事件代理機制,可以保持事件冒泡的一致性,能夠跨瀏覽器執行,甚至能夠在IE8中使用HTML5的事件。react
React 實現了一個「合成事件」層,這個事件層消除了 IE 與 W3C 標準實現之間的兼容問題。首先區分原生事件與合成事件,咱們在 componentDidMount 方法裏面經過 addEventListener 綁定的事件就是瀏覽器原生事件,使用原生事件的時候注意在 componentWillUnmount 解除綁定 removeEventListener,全部經過 JSX 這種方式綁定的事件都是綁定到「合成事件」。web
「合成事件」會以事件委託(event delegation)的方式綁定到組件最上層,而且在組件卸載(unmount)的時候自動銷燬綁定的事件。瀏覽器
在 DOM 節點上綁定事件比較消耗內存, React 則實現了一遍符合 W3C 規範的事件系統。接下來介紹該事件系統的實現原理, 事件 監聽器被綁定到整個文檔的根節點上。當事件被觸發, 瀏覽器會給出一個觸發目標事件的 DOM 節點。爲了在 DOM 的層級傳播事件, React 不會迭代 virtual DOM 的層級,而是依靠每一個 React component 各自獨立的 id 來編碼這個層級。咱們能經過簡單的字符串操做來獲取全部父級 component 的父級內容,再把事件監聽存儲在hashmap當中。下面的例子展現了事件廣播到整個virtual DOM時的傳播流程。app
clickCaptureListeners['a'](event); clickCaptureListeners['a.b'](event); clickCaptureListeners['a.b.c'](event); clickBubbleListeners['a.b.c'](event); clickBubbleListeners['a.b'](event); clickBubbleListeners['a'](event);
瀏覽器爲每一個事件和每一個listener建立一個新的事件對象,咱們能夠從這個事件對象獲取到事件的引用,可是這些事件對象也意味着高額的內存分配。爲了減輕垃圾回收的負擔,React 在啓動時就爲那些對象分配了一個內存池,當咱們須要用到某一個事件對象時就能夠從這個內存池進行復用。函數
* +------------+ .
* | DOM | . * +------------+ . * | . * v . * +------------+ . * | ReactEvent | . * | Listener | . * +------------+ . +-----------+ * | . +--------+|SimpleEvent| * | . | |Plugin | * +-----|------+ . v +-----------+ * | | | . +--------------+ +------------+ * | +-----------.--->|EventPluginHub| | Event | * | | . | | +-----------+ | Propagators| * | ReactEvent | . | | |TapEvent | |------------| * | Emitter | . | |<---+|Plugin | |other plugin| * | | . | | +-----------+ | utilities | * | +-----------.--->| | +------------+ * | | | . +--------------+ * +-----|------+ . ^ +-----------+ * | . | |Enter/Leave| * + . +-------+|Plugin | * +-------------+ . +-----------+ * | application | . * |-------------| . * | | . * | | . * +-------------+ .
框圖中的ReactBrowserEventEmitter主要用於鏈接頂層事件偵聽器,例如:編碼
EventPluginHub.putListener(‘myID’, ‘onClick’, myFunction);
接下來是對react事件系統原理框圖的理解:spa
最後咱們轉發全部的本地事件到EventPluginHub(這些本地事件由相關頂級類型來捕獲),EventPluginHub會註解每一個事件,而後分派事件。插件
React中的props表明父級分發下來的屬性,state表明組件內部能夠自行管理的狀態,而且整個React沒有數據向上回溯的能力,也就是說數據只能單向向下分發,或者自行內部消化。子組件改變父組件state的辦法只能是經過onClick等事件觸發父組件聲明好的回調,也就是父組件提早聲明好函數或方法做爲契約描述本身的state將如何變化,再將它一樣做爲屬性交給子組件使用。
這樣數據老是單向從頂層向下分發的,只有子組件回調在概念上能夠回到state頂層影響數據,這樣state必定程度上是響應式的。爲了面臨全部可能的擴展問題,最容易想到的辦法就是把全部state集中放到全部組件頂層,而後分發給全部組件。
React基於VirtualDom構建,能夠更快、更有效地完成Dom操做。React實現了一套完整的事件合成機制,可以保持事件冒泡的一致性,同時能夠實現跨瀏覽器執行,甚至能夠在IE8中使用HTML5的事件。《Secrets of the JavaScript Ninja》中講解了如何模擬 submit/focus/blur 等事件的冒泡,還講述了mouseenter 與 mouseleave 等事件的模擬。除Firefox瀏覽器外均可使用支持冒泡的 focusin/focusout 來代替 focus/blur 事件,Firefox會在捕獲階段監聽 focus/blur 事件。
submit/reset 事件會在鼠標點擊或者按回車鍵時觸發,因此能夠監聽冒泡的 click 和 keypress 事件,並判斷觸發事件的元素是否爲一個 form 元素的後代節點,而後手動觸發 submit/reset 事件。在Firefox v8.0瀏覽器下,若是做爲top-level listener之一的onmousemove事件不是掛載在document元素上,那麼當鼠標在不是該節點或者該節點所對應的子節點元素上移動時,onmousemove事件就不會被觸發。根據不一樣的瀏覽器對onmouseover事件、onscroll事件以及focusin、focusout事件的支持狀況的不一樣,react進行了有針對性的處理,如下爲react事件系統跨瀏覽器執行的部分代碼實現:
listenTo: function (registrationName, contentDocumentHandle) { var mountAt = contentDocumentHandle; var isListening = getListeningForDocument(mountAt); var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName]; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0; i < dependencies.length; i++) { var dependency = dependencies[i]; if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) { if (dependency === topLevelTypes.topWheel) { if (isEventSupported('wheel')) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt); } else if (isEventSupported('mousewheel')) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt); } else { // Firefox瀏覽器捕獲鼠標滾動事件處理 ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt); } } else if (dependency === topLevelTypes.topScroll) { if (isEventSupported('scroll', true)) { ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); } else { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topScroll, 'scroll', ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE); } } else if (dependency === topLevelTypes.topFocus || dependency === topLevelTypes.topBlur) { if (isEventSupported('focus', true)) { ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt); ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt); } else if (isEventSupported('focusin')) { // IE 瀏覽器支持的focusin和focusout事件 ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } // 保證blur和focus事件只監聽一次 isListening[topLevelTypes.topBlur] = true; isListening[topLevelTypes.topFocus] = true; } else if (topEventMapping.hasOwnProperty(dependency)) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt); } isListening[dependency] = true; } } }
【騰訊雲的1001種玩法】 徵文活動技術文章等你來讀! 點擊查看詳情