react: v15.0.0

本文讲 组件如何编译 以及 ReactDOM.render 的渲染过程。

babel 的编译

babel 将 React JSX 编译成 JavaScript.

在 babel 官网写一段 JSX 代码编译结果如图:

每个标签的创建都调用了 React.createElement.

源码中的两种数据结构

贯穿源码,常见的两种数据结构,有助于快速阅读源码。

ReactElement

结构如下:

{
$$typeof // ReactElement标识符
type // 组件
key
ref
props // 组件属性和children
}

是 React.createElement 的返回值。

ReactComponent

ReactComponent 这个名字有点奇怪。

结构如下:

{
_currentElement // ReactElement
... // 原型链上的方法
mountComponent, // 组件初次加载调用
updateComponent, // 组件更新调用
unmountComponent, // 组件卸载调用
}

是 ReactCompositeComponent 的 instance 类型。其余三种构造函数 ReactDOMComponent、ReactDOMTextComponent、ReactEmptyComponent 的实例结构与其相似。

React.createElement

React.createElement 实际执行的是 ReactElement.createElement。

ReactElement.createElement 接收三个参数:

  • type: string | Component
  • config: 标签上的属性
  • ...children: children元素集合

重点关注 type 和 props。

然后看 ReactElement 方法,只是做了赋值动作。

综上,我们写的代码编译后是这样的:

class C extends React.Component {
render() {
return {
type: "div",
props: {
children: this.props.value,
},
};
}
} class App extends React.Component {
render() {
return {
type: "div",
props: {
children: [
{
type: "span",
props: {
children: "aaapppppp",
},
},
"123",
{
type: C,
props: {
value: "ccc",
},
},
]
},
};
}
} ReactDOM.render(
{
type: App,
props: {},
},
document.getElementById("root")
);

ReactDOM.render

先来看下 ReactDOM.render 源码的执行过程

instantiateReactComponent

在 _renderNewRootComponent 方法中,调用了 instantiateReactComponent,生成了的实例结构类似于 ReactComponent。

instantiateReactComponent 的参数是 node,node 的其中一种格式就是 ReactElement。

根据 node & node.type 的类型,会执行不同的方法生成实例

  • ReactCompositeComponent
  • ReactDOMComponent
  • ReactDOMTextComponent
  • ReactEmptyComponent

简化如下

var instantiateReactComponent = function (node) {
if (node === null || node === false) {
return new ReactEmptyComponent(node);
} else if (typeof node === 'object') {
if (node.type === 'string') {
return new ReactDOMComponent(node);
} else {
return new ReactCompositeComponent(node);
}
} else if (typeof node === 'string' || typeof node === 'number') {
return new ReactDOMTextComponent(node);
}
}

通过四种方式实例化后的对象基本相似

var instance = {
_currentElement: node,
_rootNodeID: null,
...
}
instance.__proto__ = {
mountComponent,
updateComponent,
unmountComponent,
}

四种 mountComponent 简化如下

ReactCompositeComponent

mountComponent: function () {
// 创建当前组件的实例
this._instance = new this._currentElement.type(); // 调用组件的 render 方法,得到组件的 renderedElement
renderedElement = this._instance.render(); // 调用 instantiateReactComponent, 得到 renderedElement 的实例化 ReactComponent
this._renderedComponent = instantiateReactComponent(renderedElement); // 调用 ReactComponent.mountComponent
return this._renderedComponent.mountComponent();
}

ReactDOMComponent

react 源码中,插入 container 前使用 ownerDocument、DOMLazyTree 创建和存放节点,此处为了方便理解,使用 document.createElement 模拟。

mountComponent: function () {
var { type, props } = this._currentElement; var element = document.createElement(type); if (props.children) {
var childrenMarkups = props.children.map(function (node) {
var instance = instantiateReactComponent(node);
return instance.mountComponent();
}) element.appendChild(childrenMarkups)
} return element;
}

ReactDOMTextComponent

mountComponent: function () {
return this._currentElement;
}

ReactEmptyComponent

mountComponent: function () {
return null;
}

ReactDOM.render 简化

简化如下:

ReactDOM.render = function (nextElement, container) {
var nextWrappedElement = ReactElement(
TopLevelWrapper,
null,
null,
null,
null,
null,
nextElement
); var componentInstance = instantiateReactComponent(nextElement); var markup = componentInstance.mountComponent; container.innerHTML = markup;
}

总结

  1. babel 将 JSX 语法编译成 React.createElement 形式。
  2. 源码中用到了两个重要的数据结构
    • ReactElement
    • ReactComponent
  3. React.createElement 将我们写的组件处理成 ReactElement 结构。
  4. ReactDOM.render 传入 ReactElement 和 container, 渲染流程如下
    • 在 ReactElement 外套一层,生成新的 ReactElement
    • 实例化 ReactElement:var instance = instantiateReactComponent(ReactElement)
    • 递归生成 markup:var markup = instance.mountComponent()
    • 将 markup 插入 container:container.innerHTML = markup

whosmeya.com

React源码之组件的实现与首次渲染的更多相关文章

  1. React源码剖析系列 - 生命周期的管理艺术

    目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理.本系列文章希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期(C ...

  2. React 源码剖析系列 - 不可思议的 react diff

      简单点的重复利用已有的dom和其他REACT性能快的原理. key的作用和虚拟节点 目前,前端领域中 React 势头正盛,使用者众多却少有能够深入剖析内部实现机制和原理. 本系列文章希望通过剖析 ...

  3. React 源码剖析系列 - 生命周期的管理艺术

    目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理. 本系列文章 希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期 ...

  4. React躬行记(16)——React源码分析

    React可大致分为三部分:Core.Reconciler和Renderer,在阅读源码之前,首先需要搭建测试环境,为了方便起见,本文直接采用了网友搭建好的环境,React版本是16.8.6,与最新版 ...

  5. React源码 commit阶段详解

    转: React源码 commit阶段详解 点击进入React源码调试仓库. 当render阶段完成后,意味着在内存中构建的workInProgress树所有更新工作已经完成,这包括树中fiber节点 ...

  6. React源码解析:ReactElement

    ReactElement算是React源码中比较简单的部分了,直接看源码: var ReactElement = function(type, key, ref, self, source, owne ...

  7. react 源码之setState

    今天看了react源码,仅以记录. 1:monorepo (react 的代码管理方式) 与multirepo 相对. monorepo是单代码仓库, 是把所有相关项目都集中在一个代码仓库中,每个mo ...

  8. 读react源码准备

    git源码地址:https://github.com/facebook/react react 里面就是 react源码 react里面的react文件夹就是react源码,react源码非常的少,总 ...

  9. react源码之render

    1.最近学习react源码,刚刚入门,看了render的原理,到了fiberRoot的创建 如图:

随机推荐

  1. Java实现 蓝桥杯 算法训练 1的个数

    试题 算法训练 1的个数 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 输入正整数n,判断从1到n之中,数字1一共要出现几次.例如1123这个数,则出现了两次1.例如15,那么从1 ...

  2. 【原创】Linux中断子系统(二)-通用框架处理

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  3. CentOS8.1操作系下使用通用二进制包安装MySQL8.0(实践整理自MySQL官方)

    写在前的的话: 在IT技术日新月异的今天,老司机也可能在看似熟悉的道路上翻车,甚至是大型翻车现场!自己一个人开车过去翻个车不可怕,可怕的是带着整个团队甚至是整个公司一起翻车山崖下,解决办法就是:新出现 ...

  4. Android开发之修改Manifest中meta-data的数据

    代码 private void initFMMap() { ApplicationInfo appInfo = null; try { appInfo = this.getPackageManager ...

  5. 02.快捷键及基本dos命令

    无论是使用Windows.Linux操作系统,还是在IDE中,快捷键都是系统本身的标配,事实上,Ctrl+C.V这样的操作,可以帮我们节省大量的时间,如果在IDE中编写代码,除了代码本身,将其余所有的 ...

  6. 2.使用nexus3配置docker私有仓库

    1,配置走起 1,创建blob存储 登陆之后,先创建一个用于存储镜像的空间. 定义一个name,下边的内容会自动补全. 然后保存. 注意:实际生产中使用,建议服务器存储500G或以上. 2,创建一个h ...

  7. Firefox中input元素,不能重新获取焦点函数focus()

    js校验输入框的函数: function is_number(feild) { var strRegExp = /^\d+(\.\d{1,2})?$/; if (!strRegExp.test(fei ...

  8. Spring:扫描组件

    <context:component-scan>:扫描组件,对设置的包下面的类进行扫描,会讲加上注解的类作为Spring的组件进行加载 组件:指Spring中管理的bean ​ 作为Spr ...

  9. 数据处理一条龙!这15个Python库不可不知

    如果你是一名数据科学家或数据分析师,或者只是对这一行业感兴趣,那下文中这些广受欢迎且非常实用的Python库你一定得知道. 从数据收集.清理转化,到数据可视化.图像识别和网页相关,这15个Python ...

  10. 初识Redis的数据类型HyperLogLog

    前提 未来一段时间开发的项目或者需求会大量使用到Redis,趁着这段时间业务并不太繁忙,抽点时间预习和复习Redis的相关内容.刚好看到博客下面的UV和PV统计,想到了最近看书里面提到的HyperLo ...