Wang's blog Wang's blog
目录

React源码分析 - 挂载和渲染

# # React源码分析 - 挂载和渲染

源码基于v15.6.2分支

和Vue类似,先通过React.createElement() 生成 VNode Tree,再通过ReactDOM.render()挂载到真实DOM节点上。

 // 函数组件
 function functionComponent(props) {
 return (<h1 {...props}>test</h1>) // 最终生成JSX,JSX由babel解析为React.createElemnet
 }
 ReactDOM.render(functionComponent({field: 1}), el)
 
 // 类组件
 class A extends React.Component {
 // 类组件必须有render函数。有自身状态State和生命周期。
 render() {
 return <h1 {...props}>test</h1>
 }
 }
 ReactDOM.render(<A />, el)
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# # React.createElement

类似Vue的createElement方法,但React的config对象较为简单,都作为props传入,Vue的VDOM基于snabbdom库,传入的config对象限制较多(如:on、atrrs、props)。两者最终都是得到VDOM Tree的数据机构。

# # API

babel根据JSX中标签的首字母来判断是原生DOM组件,还是自定义React组件。如果首字母大写,则为React组件。这也是为什么ES6中React组件类名必须大写的原因

 <div className="title" ref="example">
 <span>123</span> // 原生DOM组件,首字母小写
 <ErrorPage title='123456' desc={[]}/> // 自定义组件,首字母大写
 </div>
 
1
2
3
4
5
 // JSX转译后js
 React.createElement(
 // type,标签名,原生DOM对象为String
 'div',
 // config,属性
 {
 className: 'title',
 ref: 'example'
 },
 // children,子元素
 React.createElement('span', null, '123'),
 React.createElement(
 // type,标签名,React自定义组件的type不为String.
 // _errorPage2.default为从其他文件中引入的React组件
 _errorPage2.default,
 {
 title: '123456',
 desc: []
 }
 )
 )
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# # 源码

 // package/react/src/ReactElement.js
 ReactElement.createElement = function (type, config, children) {
 var propName;
 
 // 初始化参数
 var props = {};
 var key = null;
 var ref = null;
 var self = null;
 var source = null;
 
 // 从config中提取出内容,如ref key props
 if (config != null) {
 ref = config.ref === undefined ? null : config.ref;
 key = config.key === undefined ? null : '' + config.key;
 self = config.__self === undefined ? null : config.__self;
 source = config.__source === undefined ? null : config.__source;
 
 // 提取出config中的prop,放入props变量中
 for (propName in config) {
 if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
 props[propName] = config[propName];
 }
 }
 }
 
 // 处理children,挂到props的children属性下
 var childrenLength = arguments.length - 2;
 if (childrenLength === 1) {
 // 只有一个参数时,直接挂到children属性下,不是array的方式
 props.children = children;
 } else if (childrenLength > 1) {
 // 不止一个时,放到array中,然后将array挂到children属性下
 var childArray = Array(childrenLength);
 for (var i = 0; i < childrenLength; i++) {
 childArray[i] = arguments[i + 2];
 }
 props.children = childArray;
 }
 
 // 取出组件类中的静态变量defaultProps,并给未在JSX中设置值的属性设置默认值
 if (type && type.defaultProps) {
 var defaultProps = type.defaultProps;
 for (propName in defaultProps) {
 if (props[propName] === undefined) {
 props[propName] = defaultProps[propName];
 }
 }
 }
 
 // 返回一个ReactElement对象
 return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
 };
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 var ReactElement = function (type, key, ref, self, source, owner, props) {
 var element = {
 $$typeof: REACT_ELEMENT_TYPE, // 常量Symbol.for('react.element')
 
 // ReactElement对象上的四个变量,特别关键
 type: type, // 关键的识别类型,自定义组件一般为function,dom元素一般为string(tag)
 key: key, // VDOM diff需要,提升性能
 ref: ref, // 真实DOM的引用
 props: props, // 子结构相关信息(有则增加children字段/没有为空)和组件属性(如style)
 
 _owner: owner
 };
 return element;
 }
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# # ReactDOM.render

  1. 根据ReactDOM.render()传入不同的参数,React内部会创建四大类封装组件,记为componentInstance。
  2. 而后将其作为参数传入mountComponentIntoNode方法中,由此获得组件对应的HTML,记为变量markup。
  3. 将真实的DOM的属性innerHTML设置为markup,即完成了DOM插入。

简化源码流程:

  • ReactDOM.render() src/renderers/dom/ReactDOM.js
    • ReactMount.render --> ReactMount._renderSubtreeIntoContainer src/renderers/dom/client/ReactMount.js 1. 顶层包装。判断更新还是初始化,更新流程略 2. 初始化:ReactMount._renderNewRootComponent 1. instantiateReactComponent重要,不同的type,创建不同的组件实例(4种)。主要为了方便生成html时,直接调用组件实例的mountComponent。return new Component(type)。下节具体分析。 2. batchedMountComponentIntoNode --> mountComponentIntoNode 创建事务,插入真实的DOM节点 1. wrapperInstance.mountComponent重要 生成组件实例对应的html代码。下节具体分析。 2. ReactMount._mountImageIntoNode 设置contaner的innerHTML
 // src/renderers/dom/ReactDOM.js
 var ReactDOM = {
 render: ReactMount.render,
 };
 
 // src/renderers/dom/client/ReactMount.js
 // nextElement即为AST对象(VNode Tree)
 render: function(nextElement, container, callback) {
 return ReactMount._renderSubtreeIntoContainer(
 null,
 nextElement,
 container,
 callback,
 );
 }
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 _renderSubtreeIntoContainer: function(
 parentComponent,
 nextElement,
 container,
 ) {
 // 顶层包装
 // VNode.type = TopLevelWrapper,TopLevelWrapper是个函数
 var nextWrappedElement = React.createElement(TopLevelWrapper, {
 child: nextElement,
 });
 
 // 拿到之前的组件,更新时就进行对比
 // 初始渲染prevComponent = false
 var prevComponent = getTopLevelWrapperInContainer(container);
 
 if (prevComponent) {
 // 更新流程
 var prevWrappedElement = prevComponent._currentElement;
 var prevElement = prevWrappedElement.props.child;
 if (shouldUpdateReactComponent(prevElement, nextElement)) {
 var publicInst = prevComponent._renderedComponent.getPublicInstance();
 ReactMount._updateRootComponent(
 prevComponent,
 nextWrappedElement,
 );
 return publicInst;
 } else {
 // remove container节点下的所以元素
 ReactMount.unmountComponentAtNode(container); // while (container.lastChild) {container.removeChild(container.lastChild);}}
 }
 }
 
 // 根据AST type,1.创建对应component实例,2. 实例递归生成对应的HTML,3. innerHTML挂载到真实的DOM上
 var component = ReactMount._renderNewRootComponent(
 nextWrappedElement,
 container,
 )._renderedComponent.getPublicInstance();
 
 return component;
 }
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 _renderNewRootComponent: function(
 nextElement,
 container,
 ) {
 // 创建component实例。instantiateReactComponent.js
 var componentInstance = instantiateReactComponent(nextElement, false);
 
 ReactUpdates.batchedUpdates(
 batchedMountComponentIntoNode, //回调函数
 );
 return componentInstance;
 },
 
1
2
3
4
5
6
7
8
9
10
11
12
13
 function batchedMountComponentIntoNode() {
 // 包装成事务方式
 transaction.perform(
 mountComponentIntoNode, // 回调
 );
 }
 
1
2
3
4
5
6
7
 function mountComponentIntoNode() {
 // 根据不同的组件类型(4种)类型,返回组件对应的HTML(下节详细讲述)
 // 等同于wrapperInstance.mountComponent
 var markup = ReactReconciler.mountComponent(wrapperInstance);
 
 // 给dom插入innerHTML
 ReactMount._mountImageIntoNode(
 markup,
 container,
 wrapperInstance
 );
 }
 
1
2
3
4
5
6
7
8
9
10
11
12
13
 _mountImageIntoNode: function(
 markup,
 container,
 instance
 ) {
 // 关键:container.innerHTML = markup;
 setInnerHTML(container, markup);
 // 将处理好的组件对象存储在缓存中,提高结构更新的速度。
 ReactDOMComponentTree.precacheNode(instance, container.firstChild);
 }
 
1
2
3
4
5
6
7
8
9
10
11
编辑 (opens new window)
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式

AltStyle によって変換されたページ (->オリジナル) /