title: react-Context
date: 2017-04-25 12:36:00
categories: react
tags : context
comments : true
updated :
layout :
题外话,个人不喜欢有魔法性的代码,比如context到底如何传递的,为嘛Provider可以直接通过context上下文传递
我的其他React源码分析系列 https://github.com/jimwmg/JiM...
https://github.com/jimwmg/React-
react源码
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,callback); },//step2 _renderSubtreeIntoContainer: function (parentComponent,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,shouldReuseMarkup,nextContext) ._renderedComponent.getPublicInstance(); return component; },//step3 //下面这个函数实现将ReactElement元素,转化为DOM元素并且插入到对应的Container元素中去; _renderNewRootComponent: function (nextElement,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,context); var wrapperID = componentInstance._instance.rootID; instancesByReactRootID[wrapperID] = componentInstance; return componentInstance; } } //step 4 //====================会进入到mountComponentIntoNode函数中 function batchedMountComponentIntoNode(componentInstance,context) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( /* useCreateElement */ !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement); transaction.perform(mountComponentIntoNode,null,transaction,context); ReactUpdates.ReactReconcileTransaction.release(transaction); } //step 5 //==================== function mountComponentIntoNode(wrapperInstance,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,ReactDOMContainerInfo(wrapperInstance,container),0 /* parentDebugID */ ); if (markerName) { console.timeEnd(markerName); } wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance; ReactMount._mountImageIntoNode(markup,wrapperInstance,transaction); } //step 6 //_mountImageIntoNode _mountImageIntoNode: function (markup,instance,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,0 /* parentDebugID */
[ReactReconciler.js源码地址. 其实就是执行下面这个函数:
mountComponent: function (internalInstance,hostParent,hostContainerInfo,parentDebugID) // 0 in production and for roots { //这里传进去的还是emptyObject; var markup = internalInstance.mountComponent(transaction,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,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,context); } else { //flag2 : 这里接着处理子组件 markup = this.performInitialMount(renderedElement,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,updateQueue);
_constructComponent: function (doConstruct,updateQueue) {
if (process.env.NODE_ENV !== 'production' && !doConstruct) { ReactCurrentOwner.current = this; try { return this._constructComponentWithoutOwner(doConstruct,updateQueue); } finally { ReactCurrentOwner.current = null; } } else { return this._constructComponentWithoutOwner(doConstruct,updateQueue); } },//然后 _constructComponentWithoutOwner: function (doConstruct,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,updateQueue); },this._debugID,'ctor'); } else { return new Component(publicProps,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,updateQueue); },'render'); } else { return Component(publicProps,
-
flag2 : 这里接着处理子组件
markup = this.performInitialMount(renderedElement,context);
performInitialMount: function (renderedElement,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,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; },