<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的引用这个说法更准确。