源碼看React---- ref

ref是怎麼引用到真實節點的

<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

attachRefs

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

當ref時function時,函數的參數傳入的是component.getPublicInstance()
component上面說過,有多是component

  • ReactEmptyComponent 空組件
  • ReactHostComponent 對原生HTML標籤的封裝
  • ReactCompositeComponent 用戶自定義組件

這裏不考慮空組件。主要是ReactHostComponentReactCompositeComponent
首先看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不是函數

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拿到的究竟是什麼

不少文章裏面說ref拿到的是真實DOM節點。其實這種說法很籠統。也很讓人困惑,上面看到咱們拿到的要麼是實例(咱們自定義組件)要麼是component的_hostNode屬性,這個好像不是真實DOM 啊
首先,什麼叫真實DOM?
以前我覺得嵌入DOM樹的,在頁面上顯示的就是真實DOM節點。咱們能夠打印一下
圖片描述
這還只是部分屬性。能夠看到DOM其實就是擁有大量屬性的一個對象。
圖片描述
只不過這個對象時塞入了DOM樹。
而咱們拿到的_hostNode是什麼?也是咱們經過document.createElement方法建立的element對象啊。只是這時候對象保存在了虛擬DOM中,而後再塞入真實DOM樹。
因此說_hostNode和真實DOM樹中的DOM的關係就是不一樣對象的不一樣屬性指向的同一塊存儲空間,引用着同一個值而已。

咱們雖然是經過虛擬DOM的_hostNode拿到這個值,可是對他的操做會體如今真實DOM節點上。說白了就是對象的引用賦值。因此,ref拿到的是真實DOM的引用這個說法更準確。

相關文章
相關標籤/搜索