react中context究竟是如何傳遞的-源碼分析

react中使用contextnode

基本要求就是react

  • 父組件中聲明Parent.prototype.getChildContextgit

  • 父組件中聲明Parent.childContextTypegithub

  • 子組件聲明 Child.contextType閉包

1 先看一個組件app

class BaseDataSelect extends Component {
    //只在組件從新加載的時候執行一次
    constructor(props) {
        super(props);
      //..
    }
      //other methods
}
//super其實就是下面這個函數
function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

//自執行函數
var Provider = function (_Component) {
  _inherits(Provider, _Component);
//父組件須要聲明
  Provider.prototype.getChildContext = function getChildContext() {
    return { store: this.store };
  };
//這裏其實就產生了閉包
  function Provider(props, context) {
    _classCallCheck(this, Provider);

    var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
    //這行代碼是我加的測試代碼,在控制檯輸出的就是一個Provider對象
    console.log(_this);
    
    _this.store = props.store;
    return _this;
  }

  Provider.prototype.render = function render() {
    return _react.Children.only(this.props.children);
  };
  //父組件須要聲明
Provider.childContextTypes = {store:PropTypes.storeShape.isRequired,}
  return Provider;
}(_react.Component);

對,就是咱們經常使用的Provider組件;less

實際中的運用(App. 是通過connect過的組件)ide

const store = createStore(reducer)
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

2 那麼傳遞context的工做是由誰來作的呢?固然是react了;函數

ReacrDOM.render其實就是ReactMount.render函數;oop

如下是react如何將ReactElement掛載到實際DOM元素上的step過程;

ReactMount.js源碼地址

var ReactMount = {
  //nextElement就是ReactELement,jsx語法將組件或者div,span等轉化爲一個ReactElement對象
  //這裏就是Provider組件生成的ReactElement對象;
  //step1 
  render: function (nextElement, container, callback) {
    //將ReactElement對象和container元素傳遞給_renderSubtreeIntoContainer函數;
    return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
  },
  //step2 
  _renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback){
    .....//具體源碼看上面源碼地址
    var nextContext;
    if (parentComponent) {
      //parentComponent爲null ;
      var parentInst = ReactInstanceMap.get(parentComponent);
      nextContext = parentInst._processChildContext(parentInst._context);
    } else {
      //因此傳遞下去的nextContext = enmtyObject;
      nextContext = emptyObject;
    }
    //.....
    var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)  ._renderedComponent.getPublicInstance();

    return component;
  },
  //step3 
  //下面這個函數實現將ReactElement元素,轉化爲DOM元素而且插入到對應的Container元素中去;
  _renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
    //instantiateReactComponent(nextElement, false)函數返回一個組件的實例,該函數源碼下面會解釋;
    var componentInstance = instantiateReactComponent(nextElement, false);

    // The initial render is synchronous but any updates that happen during
    // rendering, in componentWillMount or componentDidMount, will be batched
    // according to the current batching strategy.
    //這個函數是真正的將ReactElement元素插入到DOM元素的,會進入到batchedMountComponentIntoNode函數中;
    ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);

    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;

    return componentInstance;    

  }
}
//step 4
//====================會進入到mountComponentIntoNode函數中
function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    /* useCreateElement */
    !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
  transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
  ReactUpdates.ReactReconcileTransaction.release(transaction);
}
//step 5
//====================
function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
  var markerName;
  if (ReactFeatureFlags.logTopLevelRenders) {
    var wrappedElement = wrapperInstance._currentElement.props.child;
    var type = wrappedElement.type;
    markerName = 'React mount: ' + (typeof type === 'string' ? type : type.displayName || type.name);
    console.time(markerName);
  }
  
  //markup是通過解析成功的HTML元素,該元素經過_mountImageIntoNode加載到對應的DOM元素上;
  //注意通過上面的函數層層調用,最後到這裏的context仍是emptyObject
  var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */
                                             );

  if (markerName) {
    console.timeEnd(markerName);
  }

  wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
  ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}
//step 6
//_mountImageIntoNode
_mountImageIntoNode: function (markup, container, instance, shouldReuseMarkup, transaction) {
  !isValidContainer(container) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'mountComponentIntoNode(...): Target container is not valid.') : _prodInvariant('41') : void 0;

  if (shouldReuseMarkup) {
    var rootElement = getReactRootElementInContainer(container);
    if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
      ReactDOMComponentTree.precacheNode(instance, rootElement);
      return;
    } else {
      var checksum = rootElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
      rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);

      var rootMarkup = rootElement.outerHTML;
      rootElement.setAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum);

      var normalizedMarkup = markup;
      var diffIndex = firstDifferenceIndex(normalizedMarkup, rootMarkup);
      var difference = ' (client) ' + normalizedMarkup.substring(diffIndex - 20, diffIndex + 20) + '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20);

   
  if (transaction.useCreateElement) {
    while (container.lastChild) {
      container.removeChild(container.lastChild);
    }
    DOMLazyTree.insertTreeBefore(container, markup, null);
  } else {
   // 利用innerHTML將markup插入到container這個DOM元素上
      setInnerHTML(container, markup);
      // 將instance(Virtual DOM)保存到container這個DOM元素的firstChild這個原生節點上
    ReactDOMComponentTree.precacheNode(instance, container.firstChild);

  }

  if (process.env.NODE_ENV !== 'production') {
    var hostNode = ReactDOMComponentTree.getInstanceFromNode(container.firstChild);
    if (hostNode._debugID !== 0) {
      ReactInstrumentation.debugTool.onHostOperation({
        instanceID: hostNode._debugID,
        type: 'mount',
        payload: markup.toString()
      });
    }
  }
}

3 context如何傳遞的?

step2 - step5中開始出現context進行往下傳遞;這裏傳遞的一直是emptyObject;

主要看下step5中

var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */

[ReactReconciler.js源碼地址. 其實就是執行下面這個函數:

mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID) // 0 in production and for roots
  {
    //這裏傳進去的仍是emptyObject;
    var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
    if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    if (process.env.NODE_ENV !== 'production') {
      if (internalInstance._debugID !== 0) {
        ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID);
      }
    }
    return markup;
  },

對於internalInstance是React組件,而不是宿主DOM元素的狀況;

ReactCompositeComponent.js源碼地址

注意這裏internalInstance.mountComponent其實就是ReactCompositeComponent.js中的mountComponent方法;

mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
  var _this = this;
//這裏的this指的是internalInstance,也就是通過React處理ReactElement對象以後生成的React組件實例對象;
  this._context = context;
  this._mountOrder = nextMountID++;
  this._hostParent = hostParent;
  this._hostContainerInfo = hostContainerInfo;
//internalInstance._currentElement.props
  var publicProps = this._currentElement.props;
  
  //這裏這裏是第一次處理context;實際上是一個emptyObject;_processContext實現看上面連接,不放了,省得亂;
  var publicContext = this._processContext(context);
//這裏Component就是Provider函數;
  var Component = this._currentElement.type;

  var updateQueue = transaction.getUpdateQueue();

  // Initialize the public class
  var doConstruct = shouldConstruct(Component);
  //flag1: 注意這裏,這裏會真的調用Provider函數,生成 new Provider實例對象
  var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
  var renderedElement;

  // These should be set up in the constructor, but as a convenience for
  // simpler class abstractions, we set them up after the fact.
  inst.props = publicProps;
  inst.context = publicContext;
  inst.refs = emptyObject;
  inst.updater = updateQueue;

  this._instance = inst;

  // Store a reference from the instance back to the internal representation
  ReactInstanceMap.set(inst, this);
 
  var markup;
  if (inst.unstable_handleError) {
    markup = this.performInitialMountWithErrorHandling(renderedElement, hostParent, hostContainerInfo, transaction, context);
  } else {
    //flag2 : 這裏接着處理子組件
    markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
  }
  if (inst.componentDidMount) {
    if (process.env.NODE_ENV !== 'production') {
      transaction.getReactMountReady().enqueue(function () {
        measureLifeCyclePerf(function () {
          return inst.componentDidMount();
        }, _this._debugID, 'componentDidMount');
      });
    } else {
      transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }
  }

  return markup;
},
  • flag1: 注意這裏,這裏會真的調用Provider函數,生成 new Provider實例對象

    var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);

    _constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {

    if (process.env.NODE_ENV !== 'production' && !doConstruct) {
        ReactCurrentOwner.current = this;
        try {
          return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
        } finally {
          ReactCurrentOwner.current = null;
        }
      } else {
        return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
      }
    },
      //而後
      
    _constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
      var Component = this._currentElement.type;
    
      if (doConstruct) {
        if (process.env.NODE_ENV !== 'production') {
          return measureLifeCyclePerf(function () {
            //這裏其實就是new Provider(props,context) ;這個時候能夠對應到Provider源碼上看下;可是直到如今仍是沒有涉及到getChildContext()所返回的對象,是如何在子組件中能夠調用的;
            //等下次循環的時候這裏就是 new App(props,context) 這裏的context就有Provider.prototype.getChilContext返回的對象;
            return new Component(publicProps, publicContext, updateQueue);
          }, this._debugID, 'ctor');
        } else {
          return new Component(publicProps, publicContext, updateQueue);
        }
      }
    
      // This can still be an instance in case of factory components
      // but we'll count this as time spent rendering as the more common case.
      if (process.env.NODE_ENV !== 'production') {
        return measureLifeCyclePerf(function () {
          return Component(publicProps, publicContext, updateQueue);
        }, this._debugID, 'render');
      } else {
        return Component(publicProps, publicContext, updateQueue);
      }
    },
  • flag2 : 這裏接着處理子組件

    markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);

    performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {

    //注意傳進來的context基本上仍是等於emptyObject;
    var inst = this._instance;

    //這個inis就是 Provider實例對象;

    var debugID = 0;
    if (process.env.NODE_ENV !== 'production') {
      debugID = this._debugID;
    }
    
    if (inst.componentWillMount) {
      if (process.env.NODE_ENV !== 'production') {
        measureLifeCyclePerf(function () {
          return inst.componentWillMount();
        }, debugID, 'componentWillMount');
      } else {
        inst.componentWillMount();
      }
      // When mounting, calls to `setState` by `componentWillMount` will set
      // `this._pendingStateQueue` without triggering a re-render.
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }
    
    // If not a stateless component, we now render
    if (renderedElement === undefined) {
      //這個其實就是Provider的子組件 <App /> 也是一個ReactElement對象;
      renderedElement = this._renderValidatedComponent();
    }
    
    var nodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedNodeType = nodeType;
    var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */);
    this._renderedComponent = child;

    //這裏又輪迴到了ReactReconciler.js中的mountComponent

    //若是child組件仍是React組件,而不是宿主DOM元素,那麼就會一直遞歸,直到child是宿主DOM元素;
    //就不會輪迴到ReactCompositeComponent.js中的mountComponent;
    //對於仍是React組件的狀況下,仍是會執行ReactCompositeComponent.js中mountComponent
    //注意這個時候傳遞給該函數的context參數的值是 this._processChildContext(context)
    //此時傳入的child就是 App  子組件(connect後的高階組件) 生成的React組件實例
    //而後生成的高階組件 App  就會將經過Provider傳遞過來的store對象上的相關接口傳遞給被包裹的組件,做爲被包裹組件的props;
    //文章開頭有連接react其餘源碼分析,上面有Provider分析文章;
    var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);

    //this._processChildContext(context) 此時的this指的是Provider組件通過React處理後生成的instantiateReactComponent(nextElement, false);react實例對象;上面的child也是同樣的道理;

    if (process.env.NODE_ENV !== 'production') {
      if (debugID !== 0) {
        var childDebugIDs = child._debugID !== 0 ? [child._debugID] : [];
        ReactInstrumentation.debugTool.onSetChildren(debugID, childDebugIDs);
      }
    }
    
    return markup;

    },

這裏重點看下

_processChildContext: function (currentContext) {
    var Component = this._currentElement.type;
  //這個inst就是Provider組件new以後的實例對象
    var inst = this._instance;
    var childContext;

    if (inst.getChildContext) {
      if ("development" !== 'production') {
        ReactInstrumentation.debugTool.onBeginProcessingChildContext();
        try {
          //這裏經過Provider.prototype.getChildContext上獲得context值
          childContext = inst.getChildContext();
        } finally {
          ReactInstrumentation.debugTool.onEndProcessingChildContext();
        }
      } else {
        childContext = inst.getChildContext();
      }
    }
    if (childContext) {
      return _assign({}, currentContext, childContext);
    }
    return currentContext;
  },

個人其餘React源碼分析系列 https://github.com/jimwmg/JiM...

https://github.com/jimwmg/React-react源碼

相關文章
相關標籤/搜索