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…