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...