<input type="text" ref={(node)=>{this.myInput = node}}/>
這是咱們在組件中書寫的樣式。咱們知道,在HTML中這是個DOM節點,可是在React會經由jsx轉化爲React.createElement(...)
因此,這裏其實至關於一個函數。node
ReactElement.createElement = function(type, config, children) { var propName; // Reserved names are extracted var props = {}; var key = null; var ref = null; var self = null; var source = null; if (config != null) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { key = '' + config.key; } ... return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); };
巴拉巴拉的細節邏輯就不看了,總之呢就是生成了一個ReactElement對象,而ref會保存爲該對象的一個屬性。
當這個reactElement開始掛載時會先將他包裝成一個ReactComponent。(沒錯,驚喜不驚喜,之外不意外,你隨便寫的一div,input標籤都是先被封裝成組件再掛載。)實現代碼以下,固然,這裏精簡了不少代碼。react
function instantiateReactComponent(node, shouldHaveDebugID) { var instance; console.log(node); if (node === null || node === false) { instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { var element = node; var type = element.type; // Special case string values if (typeof element.type === 'string') { instance = ReactHostComponent.createInternalComponent(element); } else { instance = new ReactCompositeComponentWrapper(element); } } else if (typeof node === 'string' || typeof node === 'number') { instance = ReactHostComponent.createInstanceForText(node); } return instance; }
生成組件之後就會調用ReactReconciler.mountComponent
進行組件掛載。app
mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID) // 0 in production and for roots { //這裏返回的markup就是渲染好的部分虛擬DOM樹 var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID); if (internalInstance._currentElement && internalInstance._currentElement.ref != null) { transaction.getReactMountReady().enqueue(attachRefs, internalInstance); } return markup; },
從代碼中能夠看出,當ReactElement生成虛擬DOM節點之後,會進行判斷,若是該節點有ref引用。則會把attachRefs函數和該節點傳入回調函數序列。
那麼這個回調序列又在哪兒觸發的呢,原理仍是transaction
機制。函數
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
這段代碼裏的transaction是ReactReconcileTransaction
。
而ReactReconcileTransaction
包裹了三層wrapperthis
var SELECTION_RESTORATION = { /** * @return {Selection} Selection information. * 返回選中的信息 */ initialize: ReactInputSelection.getSelectionInformation, /** * @param {Selection} sel Selection information returned from `initialize`. */ close: ReactInputSelection.restoreSelection, }; var EVENT_SUPPRESSION = { initialize: function() { //先肯定當前是否能夠進行事件處理。 var currentlyEnabled = ReactBrowserEventEmitter.isEnabled(); //防止這次事件處理過程當中會發生其餘transaction裏面的事件 ReactBrowserEventEmitter.setEnabled(false); return currentlyEnabled; }, close: function(previouslyEnabled) { ReactBrowserEventEmitter.setEnabled(previouslyEnabled); }, }; // 經過CallbackQueue回調函數隊列機制,即this.reactMountReady // 執行this.reactMountReady.enqueue(fn)注入componentDidMount、componentDidUpdate方法 // 經過Transaction添加前、後置鉤子機制 // 前置鉤子initialize方法用於清空回調隊列;close用於觸發回調函數componentDidMount、componentDidUpdate執行 var ON_DOM_READY_QUEUEING = { initialize: function() { this.reactMountReady.reset(); }, close: function() { this.reactMountReady.notifyAll(); }, }; var TRANSACTION_WRAPPERS = [ SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING, ]; var Mixin = { getTransactionWrappers: function() { return TRANSACTION_WRAPPERS; } }
其中的ON_DOM_READY_QUEUEING
正是子掛載結束後執行回調序列中的回調函數。
而ReactReconcileTransaction
事務則是在第一進入項目加載第一個節點時開啓的。
大概流程以下
spa
ref引用主要是ReactRef中的attachRef函數rest
//ref是咱們標籤中書寫的ref //component是書寫ref的組件。上面提過,哪怕是input也會被封裝成組件再掛載。 //owner是component所在的組件 function attachRef(ref, component, owner) { if (typeof ref === 'function') { ref(component.getPublicInstance()); } else { // Legacy ref ReactOwner.addComponentAsRefTo(component, ref, owner); } }
能夠看到,正是在這邊組件獲取到了ref引用。code
當ref時function時,函數的參數傳入的是component.getPublicInstance()
component上面說過,有多是component
這裏不考慮空組件。主要是ReactHostComponent
和ReactCompositeComponent
首先看ReactCompositeComponent
。這個簡單orm
getPublicInstance: function () { var inst = this._instance; return inst; },
直接傳入了實例化對象。經過這個方法,咱們能夠經過ref隨時調用子組件的內部方法。
再看ReactHostComponent
getPublicInstance: function() { return getNode(this); }, function getNodeFromInstance(inst) { if (inst._hostNode) { return inst._hostNode; } var parents = []; _prodInvariant('34') : void 0; inst = inst._hostParent; } for (; parents.length; inst = parents.pop()) { precacheChildNodes(inst, inst._hostNode); } return inst._hostNode; }
這裏是根據getNode獲取的引用。getNode執行的又是ReactDOMComponentTree
中的getNodeFromInstance
方法。
從這個方法咱們不能發現,這裏ref獲取到的參數就是實例的_hostNode
那麼這個_hostNode
又是什麼?參考源碼,發現組件在掛載過程當中
執行了這麼句代碼
el = ownerDocument.createElement(this._currentElement.type); ReactDOMComponentTree.precacheNode(this, el); //。。。。。我是分隔符 function getRenderedHostOrTextFromComponent(component) { var rendered; while (rendered = component._renderedComponent) { component = rendered; } return component; } function precacheNode(inst, node) { var hostInst = getRenderedHostOrTextFromComponent(inst); hostInst._hostNode = node; node[internalInstanceKey] = hostInst; }
經過當前元素類型創造節點。接着將節點塞到了組件中,保存的正是_hostNode
。
ref不是函數執行的是ReactOwner的attachRef
attachRef: function (ref, component) { var inst = this.getPublicInstance(); var publicComponentInstance = component.getPublicInstance(); var refs = inst.refs === emptyObject ? inst.refs = {} : inst.refs; refs[ref] = publicComponentInstance; },
而這裏傳入的component正是有ref屬性的組件(前面說過,哪怕寫在input上,掛載時也是包裝成組件再掛載),此時的ReactOwner(this)是component所在的組件。
能夠看到此時將引用賦值給了this.refs的一個屬性。屬性名正是傳入的ref。
很顯然,ref能夠若是不是function,也不必定是string。只要是合法的object key就能夠了。固然,爲了便於開發,仍是推薦使用有意義的字符
不少文章裏面說ref拿到的是真實DOM節點。其實這種說法很籠統。也很讓人困惑,上面看到咱們拿到的要麼是實例(咱們自定義組件)要麼是component的_hostNode屬性,這個好像不是真實DOM 啊
首先,什麼叫真實DOM?
以前我覺得嵌入DOM樹的,在頁面上顯示的就是真實DOM節點。咱們能夠打印一下
這還只是部分屬性。能夠看到DOM其實就是擁有大量屬性的一個對象。
只不過這個對象時塞入了DOM樹。
而咱們拿到的_hostNode是什麼?也是咱們經過document.createElement方法建立的element對象啊。只是這時候對象保存在了虛擬DOM中,而後再塞入真實DOM樹。
因此說_hostNode和真實DOM樹中的DOM的關係就是不一樣對象的不一樣屬性指向的同一塊存儲空間,引用着同一個值而已。
咱們雖然是經過虛擬DOM的_hostNode拿到這個值,可是對他的操做會體如今真實DOM節點上。說白了就是對象的引用賦值。因此,ref拿到的是真實DOM的引用這個說法更準確。