React-简单组件挂载(mount)过程

2018年12月10日 ... ☕️ 3 min read

上一篇大致看了一下简单组件渲染过程。走到internalInstance.mountComponent,没有往里细看,因为ReactCompositeComponent.mountComponent是个比较复杂的函数,主要做了几个工作:初始化组件、渲染标签(markup)、注册监听事件,react生命周期函数也在这里执行,所以这里单独记录。

本篇依然沿用之前的例子,使用简单组件,即根节点内只包含一层class类型的组件。

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}
 
ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('root')
);

本篇涉及内容的基本的函数执行流程

react-mount-call-flow

可能注意到,生命周期函数componentWillMount和componentDidMount并不处在同一个函数调用里,这是因为组件是递归解析的,后面会讲到。

从1到2.3)基本的数据结构变动

react-mount1

1.创建一个组件,用于实例化ReactCompositeComponent

-_constructComponent(
doConstruct,
publicProps, // _currentElement.props
publicContext,
updateQueue
)
 
// 创建_currentElement.type实例
inst = (TopLevelWrapper){
  rootID: 1,
  props: {
    child: {
      ...
      props: {
        "name": "World"
      }
      ...
    }
  },
  context: {},
  refs: {},
  updater: {}
}

之后在属性ReactCompositeComponent.instance加上该实例的引用,并在实例中用reactInternalInstance创建反向链接。

2.执行挂载

由于hostContainerInfo从创建一路传递进去,基本的结构没变动,这里记录一下

react-dom-container-info

基本的准备工作之后,开始在performInitialMount执行初始化挂载动作

-this.performInitialMount(
	renderedElement,
	hostParent,
	hostContainerInfo,
	transaction,
	context
)

1)执行componentWillMount和需要的state更新;

2)执行inst.render

根据inst的类型,这里执行TopLevelWrapper原型上的render函数

renderedElement = inst.render
=TopLevelWrapper.prototype.render {
    return this.props.child;
}

将renderedElement添加到属性ReactCompositeComponent._renderedComponent上

3)把上一步得到的renderedElement‘去壳’(TopLevelWrapper),并实例化为实际挂载的组件

上一篇讲过,组件是从ReactDOM一层一层包装到ReactCompositeComponent并序列化,这里做的,就是逆向操作,一步一步还原为可供实际渲染的标签。

var child = _instantiateReactComponent
=instantiateReactComponent(node /** renderedElement  **/, ...)

去除TopLevelWrapper包装

remove-top-level-wrapper

4)内一层(child)的组件挂载

步骤4)的数据结构变动

react-mount2

由于组件的根节点只有一个(记得吗,规定组件的render里只能存在一个根节点),所以这里会直接渲染child,而不用写循环。

-ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
debugID
)
-child.mountComponent //(递归调用ReactCompositeComponent.mountComponent)
inst = (ReactComponent){
      ...
      props: {
        "name": "World"
      }
      ...
}

将步骤1中的inst.props.child包装成ReactComponent实例

-performInitialMount
-_renderValidatedComponentWithoutOwnerOrContext
执行实例的渲染方法inst.render

-ReactElementValidator.createElement
=ReactElement.createElement

5)再次使用ReactReconciler.mountComponent

-ReactReconciler.mountComponent
-ReactDOMComponent.mountComponent //(递归出口)

6)转为可用的标签(markup,还不是HTML标签哦)

由document.createElement创建标签,设置attr等属性

mountChildren
=ReactChildReconciler.instantiateChildren
=traverseAllChildren // 一个字符串会生成一个ReactDOMTextComponet,由ReactReconciler.mountComponent依次处理

7)按层级执行componentDidMount。

小结

可以看出,ReactReconciler.mountComponent是唯一挂载入口,所有类型(ReactCompositeComponent、ReactDOMComponet、ReactDOMTextComponet)的挂载都是以这个函数为调度器。之前包装好的组件结构,在这个函数里被逐层拆解出来,并在中间加入了可控的用户方法。

目前还没涉及到实际的react节点变为HTML节点,即_mountImageIntoNode方法。

#react

SideEffect is a blog for front-end web development.
Code by Axiu / rss