React 是一个十分庞大的库,因为要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,致使其代码抽象化程度很高,嵌套层级很是深,阅读其源码是一个很是艰辛的过程。在学习 React 源码的过程当中,给我帮助最大的就是这个系列文章,因而决定基于这个系列文章谈一下本身的理解。本文会大量用到原文中的例子,想体会原汁原味的感受,推荐阅读原文。javascript
本系列文章基于 React 15.4.2 ,如下是本系列其它文章的传送门:
React 源码深度解读(一):首次 DOM 元素渲染 - Part 1
React 源码深度解读(二):首次 DOM 元素渲染 - Part 2
React 源码深度解读(三):首次 DOM 元素渲染 - Part 3
React 源码深度解读(四):首次自定义组件渲染 - Part 1
React 源码深度解读(五):首次自定义组件渲染 - Part 2
React 源码深度解读(六):依赖注入
React 源码深度解读(七):事务 - Part 1
React 源码深度解读(八):事务 - Part 2
React 源码深度解读(九):单个元素更新
React 源码深度解读(十):Diff 算法详解java
前面三篇文章介绍了 React 是怎么渲染普通DOM元素的,以下图所示。node
红线部分生成的markup
其实是一层一层往回传,为了方便展现就直接跳过中间层级返回了。这张图片跳过了事务(transaction)相关的调用,后面会有专门的文章介绍。算法
本篇开始介绍自定义组件是如何渲染的。segmentfault
将自定义组件命名为App
,结构以下:服务器
class App extends Component { constructor(props) { super(props); this.state = { desc: 'start', }; } render() { return ( <div className="App"> <div className="App-header"> <img src="main.jpg" className="App-logo" alt="logo" /> <h1> "Welcom to React" </h1> </div> <p className="App-intro"> { this.state.desc } </p> </div> ); } } ReactDOM.render( <App />, document.getElementById(‘root’) );
App 通过 Babel 编译后,生成以下代码:app
class App extends Component { constructor(props) { super(props); this.state = { desc: 'start', }; } render() { return React.createElement( 'div', { className: 'App' }, React.createElement( 'div', { className: 'App-header' }, React.createElement( 'img', { src: "main.jpg", className: 'App-logo', alt: 'logo' } ), React.createElement( 'h1', null, ' "Welcom to React" ' ) ), React.createElement( 'p', { className: 'App-intro' }, this.state.desc ) ); } } ReactDOM.render( React.createElement(App, null), document.getElementById(‘root’) );
ReactCompositeComponent[T]
React.createElement
建立 type 为 App 的 ReactElement[1]
。_renderSubtreeIntoContainer
里面建立 type 为 TopLevelWrapper 的 ReactElement[2]
。instantiateReactComponent
建立包装元素 ReactCompositeComponent[T]
。调用关系以下图所示:学习
ReactCompositeComponent[T]
mountComponentIntoNode
时,ReactDOMContainerInfo[ins]
会被建立并传给ReactReconciler
。ReactReconciler
会调用ReactCompositeComponent[T]
的 mountComponent 建立 TopLevelWrapper 实例。渲染普通 DOM 元素的调用关系以下图所示,自定义组件的渲染调用关系见下文:this
ReactCompositeComponent[ins]
在 performInitialMount 这步,renderedElement 就是 ReactElement[1]
:spa
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) { ... // 这里会调用 TopLevelWrapper 实例的 render 方法,获得 ReactElement[1] if (renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } ... // 返回 ReactCompositeComponent[ins] var child = this._instantiateReactComponent( renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */ ); this._renderedComponent = child; var markup = ReactReconciler.mountComponent( child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID ); return markup; },
这步与第二篇的 performInitialMount 很类似,惟一区别就是渲染普通 DOM 元素返回的是ReactDOMComponent
,而渲染自定义组件返回的是包装好的自定义组件ReactCompositeComponent[ins]
。
调用关系以下图所示:
ReactCompositeComponent[ins]
在 performInitialMount 的后半部分,ReactReconciler.mountComponent 实际上会调用 ReactCompositeComponent[ins] 的 mountComponent。这里的关键代码是
... // 建立 App 组件的实例 var inst = this._constructComponent( doConstruct, publicProps, publicContext, updateQueue ); ...
通过这步后,ReactCompositeComponent[ins]._instance 等于 App[ins]。像以前同样,mountComponent 又会调用自身的 performInitialMount:
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) { ... // 这里会调用 App 实例的 render 方法,而 render 的返回值是 React.createElement 的嵌套调用。 if (renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } ... // 返回 ReactDOMComponent[6] var child = this._instantiateReactComponent( renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */ ); this._renderedComponent = child; var markup = ReactReconciler.mountComponent( child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID ); return markup; },
React.createElement 的嵌套调用是指:
render() { return React.createElement( // scr: -----------> 5) 'div', { className: 'App' }, React.createElement( // scr: -----------> 3) 'div', { className: 'App-header' }, React.createElement( // scr: -----------> 1) 'img', { src: "main.jpg", className: 'App-logo', alt: 'logo' } ), React.createElement( // scr: -----------> 2) 'h1', null, ' "Welcom to React" ' ) ), React.createElement( // scr: -----------> 4) 'p', { className: 'App-intro' }, this.state.desc ) ); }
这里 React.createElement 的调用顺序是先调用做为参数的 children,再调用父级。调用顺序已在代码中注释。
接下来的 _instantiateReactComponent 会返回ReactDOMComponent
,就触及到真正的 DOM 操做了。先看图,这部份内容将在下回分解~