6

react中使用context

基本要求就是

  • 父组件中声明Parent.prototype.getChildContext

  • 父组件中声明Parent.childContextType

  • 子组件声明 Child.contextType

1 先看一个组件

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组件;

实际中的运用(App. 是经过connect过的组件)

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

2 那么传递context的工作是由谁来做的呢?当然是react了;

ReacrDOM.render其实就是ReactMount.render函数;

以下是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源码


jimwmg
151 声望4 粉丝

点点滴滴,不忘初心