Photo by Joshua Sortino on Unsplash
本文也同时发表在我的博客和HACKERNOON
深入理解React源码 - 首次渲染 IV (本篇)
咱们在上几篇讲完了简单组件的渲染过程。这次我们用一个自定义组件(我们平时开发用的那种)来探索渲染流程的更多支线。
如果本篇涉及的函数和代码在之前详细讨论过,我会用{}来做引用
App
非常像我在最开始给出来的的那个组件,我们上次觉得这个组件太难了(所以转头讨论的简单组件渲染)。但经历完前面几次的代码阅读后我们也升了不少级。现在,对于我们来说这个原先的boss变成小怪也就难免了。
import React, { Component } from ‘react’;
import logo from ‘./logo.svg’;
import ‘./App.css’;
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>
);
}
}
export default App;
App@App.js
之前提到过,这个组件会被这么来渲染:
ReactDOM.render(
<App />,
document.getElementById(‘root’)
);
再来上babel后的代码:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
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
)
);
}
}
export default App;
...
ReactDOM.render(React.createElement(App, null), document.getElementById('root'));
这里我们先把Component
看成一个普通的基类,因为她的其它成员方法暂时还用不到。
对于和简单组件重复的路径我们这次可以过快点。
构建顶层包装组件`ReactCompositeComponent[T]
`
目标数据结构:
这一步和简单组件是一样的,所以就简单说一下:
1)创建ReactElement.createElement(type, config, children)
创建ReactElement[1]
(这次App
传给了type
,而config
,children
则是null
);
2)在_renderSubtreeIntoContainer()
里创建ReactElement[2]
;
3)用instantiateReactComponent()
创建我们需要的包装元素。
ReactElement.createElement(type, // scr: -------------> App
config, // scr: -------------> null
children // scr: -------------> null
) // scr: ------------------------------------------------------> 1)
ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer(
parentComponent, // scr: ----> null
nextElement, // scr: ----> ReactElement[1]
container, // scr: ----> document.getElementById(‘root’)
callback’ // scr: ----> undefined
) // scr: ------------------------------------------------------> 2)
|-instantiateReactComponent( // scr: -------------------------> 3)
node, // scr: ------> ReactElement[2]
shouldHaveDebugID /* false */
)
|-ReactCompositeComponentWrapper(
element // scr: ------> ReactElement[2]
);
|=ReactCompositeComponent.construct(element /* same */)
这个是我们在{第一篇}里面讲的。
初始化`ReactCompositeComponent[T]`
目标数据结构:
这一步也是一样的:
1)ReactDOMContainerInfo[ins]
代表用document.getElementById(‘root’)
创建的DOM元素;
2)TopLevelWrapper
会被实例化,然后被设置给ReactCompositeComponent[T]._currentElement
,同时其它的属性也会被初始化;
3)mountComponentIntoNode
是表里层的交叉口,在这个函数里面ReactCompositeComponent[T].mountComponent
会返回一个完整的DOMLazyTree
,最后被里层函数ReactMount._mountImageIntoNode
渲染成DOM元素。
ReactDOM.render ___
|=ReactMount.render(nextElement, container, callback) |
|=ReactMount._renderSubtreeIntoContainer() |
|-ReactMount._renderNewRootComponent() |
|-instantiateReactComponent() |
|~batchedMountComponentIntoNode() upper half
|~mountComponentIntoNode() (platform agnostic)
|-ReactReconciler.mountComponent() // scr-----> 1) |
|-ReactCompositeComponent[T].mountComponent() scr:> 2)3)
... _|_
... lower half
|-_mountImageIntoNode() (HTML DOM specific)
这个是我们在{第二篇}里面讲的。
除了一下传参的细小差别外,其实顶层包装类的操作是和简单组件几本一致的。这些操作完成后我们就到了自定义组件特有的第一个叉道。
`ReactCompositeComponent[T].performInitialMount()` —用`ReactElement[1]`创建`ReactCompositeComponent`
这一步主要来去除顶层包装类,然后创建另一个ReactCompositeComponent
来代表我们自定义的App
组件。
目标数据结构:
调用栈:
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
/* we are here */ |
|-ReactCompositeComponent[T].performInitialMount( |
renderedElement, // scr: -------> undefined |
hostParent, // scr: -------> null upper half
hostContainerInfo, // scr: -------> | ReactDOMContainerInfo[ins] |
transaction, // scr: -------> not of interest |
context, // scr: -------> not of interest |
) |
这一步和在{第二篇}中讨论的performInitialMount()
非常类似。其中唯一的区别是_instantiateReactComponent
会基于ReactElement[1]
创建 代表App
的ReactCompositeComponent
,而不是ReactDOMComponent
。简单来说:
1)调用_renderValidatedComponent()
,紧跟着在这个函数里再调用 TopLevelWrapper.render()
来解出ReactElement[1]
;2)用_instantiateReactComponent
实例化 ReactCompositeComponent[ins])
;和3)通过ReactReconciler
(递归)调用ReactCompositeComponent[ins].mountComponent
,然后我们到下一步。
performInitialMount: function (
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context)
{
var inst = this._instance;
...
if (inst.componentWillMount) {
... // scr: we did not define componentWillMount() in App
}
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent(); // scr: > 1)
}
var nodeType = ReactNodeTypes.getType(renderedElement); // scr: -> the type is ReactNodeTypes.Composite this time
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
); // scr: ----------------------------------------------> 2)
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID); // scr: ----------------------------------------------> 3)
...// scr: DEV code
return markup;
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
`ReactCompositeComponent[1].mountComponent()` — initialize `ReactCompositeComponent[1]`
目标数据结构:
调用栈:
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
|-ReactCompositeComponent[T].performInitialMount() upper half
|-ReactReconciler.mountComponent() |
/* we are here */ |
|-ReactCompositeComponent[1].mountComponent(same) |
和{第二篇}里一样,ReactCompositeComponent[T].mountComponent()
里面最重要的一步是用ReactCompositeComponent[ins]._currentElement
(ReactElement[1]
)实例化App
。
把这个活干了的关键行是:
...
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
);
...
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
在这个函数里App
的构造函数会被调用。
...
constructor(props) {
super(props);
this.state = {
desc: 'start',
};
}
...
// 从开头拷的
然后(我们叫她)App[ins]
被赋值给ReactCompositeComponent[ins]._instance
,并且ReactInstanceMap
被用来做反向链接。
其它操作包括:1)App[ins].props
引用ReactElement[1].props
;和2)自增全局变量nextMountID
将ReactCompositeComponent[ins]._mountOrder
设置成2。
这里很重要的一点是App[ins].render()
这个在文章开头定义的另一个函数。不像TopLevelWrapper[ins].render()
会返回一个实在的ReactElement
对象,App[ins].render()
会在被调用时依赖React.createElement()
来动态创建ReactElement。这个我们马上就会看到了。
之于这一步和{第二篇}讨论的ReactCompositeComponent[T]
初始化非常类似,我们只用大致浏览一遍这个函数的实现就可以了。
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
// scr: this ------> ReactCompositeComponent[T]
) {
...
this._mountOrder = nextMountID++; // scr: ----------------------> 2)
...
var publicProps = this._currentElement.props; // scr: ------------> { child: ReactElement[1] }
...
// scr: -------------------------------------------------> critical)
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
); // scr: ----------> call TopLevelWrapper’s constructor
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.
// scr: --------------------------------------------------------> 1)
inst.props = publicProps; // scr: ----> { child: ReactElement[1] }
…
// scr: -------------------------------------------------> critical)
this._instance = inst; // scr: ---------------------------------> link the ReactCompositeComponent[T] to the TopLevelWrapper instance
// Store a reference from the instance back to the internal representation
ReactInstanceMap.set(inst, this); // scr: ----------------------> link the TopLevelWrapper instance back to ReactCompositeComponent[T]
…
var markup;
if (inst.unstable_handleError) { // scr: -----------------------> false, TopLevelWrapper.prototype.unstable_handleError is not defined
…
} else {
// scr: -----------------------------------------------------> next)
markup = this.performInitialMount( // scr: a initial at the end?
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
);
}
…
return markup;
}
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
`ReactCompositeComponent[ins].performInitialMount()` — create a `ReactDOMComponent`
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
|-ReactCompositeComponent[T].performInitialMount() upper half
|-ReactReconciler.mountComponent() |
/* we are here */ |
|-ReactCompositeComponent[1].mountComponent() |
|-this.performInitialMount() |
|-this._renderValidatedComponent() |
|-instantiateReactComponent() _|_
|-ReactDOMComponent[6].mountComponent() lower half
我们又到这个函数了:
performInitialMount: function (
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context)
{
var inst = this._instance;
...
if (inst.componentWillMount) {
... // scr: we did not define componentWillMount() in App
}
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent(); // scr: > 1)
}
var nodeType = ReactNodeTypes.getType(renderedElement); // scr: -> the type is ReactNodeTypes.Host this time
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
); // scr: ----------------------------------------------> 2)
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID); // scr: ----------------------------------------------> 3)
...// scr: DEV code
return markup;
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
在ReactDOMComponent
(我们知道这个类用来直接操作DOM)被创建之前 ,我们需要先把在App[ins]
里定义的ReactElement
们解出来。这里还是用App[ins].render()
。下一行函数在 {第二篇}中讨论过。
...
renderedElement = this._renderValidatedComponent();
...
然后,这个App[ins].render()
会触发
1) 连锁调用React.createElement()
为了理解ReactElement
树的创建过程,我们先重新看一下App.render()
的实现:
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
)
);
}
// 从开头拷的
在这段代码片段里我还给出了createElement()
们的调用顺序。这个顺序遵循一个非常简单的原则:先(用createElement()
)拿参数,再调(createElement()
)函数。
然后再看每个ReactElement
的创建{第一篇}就有谱了。
React.createElement( // scr: --------------------------------> 1)
‘img’,
{ src: "main.jpg", className: ‘App-logo’, alt: ‘logo’ }
),
创建ReactElement[2]
:
;然后
React.createElement( // scr: --------------------------------> 2)
‘h1’,
null,
‘Welcome to React’
)
创建ReactElement[3]
:
(现在 3)要的两个参数好了)
;然后
React.createElement( // scr: -----------> 3)
'div',
ReactElement[2],
ReactElement[3]
),
创建ReactElement[4]
:
;然后
React.createElement( // scr: -----------> 4)
'p',
{ className: 'App-intro' },
this.state.desc
)
创建ReactElement[5]
:
(现在 5)要的两个参数也好了)
;然后
return React.createElement( // scr: -----------> 5)
'div',
{ className: 'App' },
ReactElement[4],
ReactElement[5]
)
创建ReactElement[6]
:
合在一起:
ReactElement
树就是这么建立的。这棵树的根节点会被赋值给renderedElement
2)`ReactCompositeComponent[ins]._instantiateReactComponent()` — Create `ReactDOMComponent[6]`
目标数据结构:
上一步建立的element树在下面这行代码被用于创建ReactDOMComponent[6]
(在_instantiateReactComponent()
里){第二篇}
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
3)ReactReconciler.mountComponent()
会调用ReactDOMComponent[6].mountComponent()
然后逻辑处理到了里层。
待续...
今天先写到这。如果您觉得这篇不错,可以点赞或关注这个专栏。
感谢阅读!👋
Originally published at
Understanding The React Source Code - Initial Rendering (Simple Component) Iholmeshe.me.