React源码之组件的实现与首次渲染
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;
}
总结
- babel 将 JSX 语法编译成 React.createElement 形式。
- 源码中用到了两个重要的数据结构
- ReactElement
- ReactComponent
- React.createElement 将我们写的组件处理成 ReactElement 结构。
- ReactDOM.render 传入 ReactElement 和 container, 渲染流程如下
- 在 ReactElement 外套一层,生成新的 ReactElement
- 实例化 ReactElement:var instance = instantiateReactComponent(ReactElement)
- 递归生成 markup:var markup = instance.mountComponent()
- 将 markup 插入 container:container.innerHTML = markup
React源码之组件的实现与首次渲染的更多相关文章
- React源码剖析系列 - 生命周期的管理艺术
目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理.本系列文章希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期(C ...
- React 源码剖析系列 - 不可思议的 react diff
简单点的重复利用已有的dom和其他REACT性能快的原理. key的作用和虚拟节点 目前,前端领域中 React 势头正盛,使用者众多却少有能够深入剖析内部实现机制和原理. 本系列文章希望通过剖析 ...
- React 源码剖析系列 - 生命周期的管理艺术
目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理. 本系列文章 希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期 ...
- React躬行记(16)——React源码分析
React可大致分为三部分:Core.Reconciler和Renderer,在阅读源码之前,首先需要搭建测试环境,为了方便起见,本文直接采用了网友搭建好的环境,React版本是16.8.6,与最新版 ...
- React源码 commit阶段详解
转: React源码 commit阶段详解 点击进入React源码调试仓库. 当render阶段完成后,意味着在内存中构建的workInProgress树所有更新工作已经完成,这包括树中fiber节点 ...
- React源码解析:ReactElement
ReactElement算是React源码中比较简单的部分了,直接看源码: var ReactElement = function(type, key, ref, self, source, owne ...
- react 源码之setState
今天看了react源码,仅以记录. 1:monorepo (react 的代码管理方式) 与multirepo 相对. monorepo是单代码仓库, 是把所有相关项目都集中在一个代码仓库中,每个mo ...
- 读react源码准备
git源码地址:https://github.com/facebook/react react 里面就是 react源码 react里面的react文件夹就是react源码,react源码非常的少,总 ...
- react源码之render
1.最近学习react源码,刚刚入门,看了render的原理,到了fiberRoot的创建 如图:
随机推荐
- Java实现 LeetCode 532 数组中的K-diff数对(双指针,滑动窗口)
532. 数组中的K-diff数对 给定一个整数数组和一个整数 k, 你需要在数组里找到不同的 k-diff 数对.这里将 k-diff 数对定义为一个整数对 (i, j), 其中 i 和 j 都是数 ...
- Java实现 蓝桥杯VIP 算法训练 快速排序
import java.util.Scanner; public class 快速排序 { public static void main(String[] args){ Scanner sc=new ...
- Java实现 LeetCode 219 存在重复元素 II(二)
219. 存在重复元素 II 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k. 示 ...
- Java实现 LeetCode 166 分数到小数
166. 分数到小数 给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数. 如果小数部分为循环小数,则将循环的部分括在括号内. 示例 1: 输入 ...
- Java实现 蓝桥杯 历届真题 稍大的串
串可以按照字典序进行比较.例如: abcd 小于 abdc 如果给定一个串,打乱组成它的字母,重新排列,可以得到许多不同的串,在这些不同的串中,有一个串刚好给定的串稍微大一些.科学地说:它是大于已知串 ...
- 第二届蓝桥杯C++B组国(决)赛真题
以下代码仅供参考,解答部分来自网友,对于正确性不能保证,如有错误欢迎评论 四方定理. 数论中有著名的四方定理:所有自然数至多只要用四个数的平方和就可以表示. 我们可以通过计算机验证其在有限范围的正确性 ...
- 温故知新-Mysql锁&事务&MVCC
文章目录 锁概述 锁分类 MyISAM 表锁 InnoDB 行锁 事务及其ACID属性 InnoDB 的行锁模式 注意 MVCC InnoDB 中的 MVCC 参考 你的鼓励也是我创作的动力 Post ...
- 第一章03-Activity的启动模式
Activity的LaunchMode Android中提供了四中Activity的启动模式 1. standard 2. singleTop 3. singleTask 4. signleInsta ...
- Copy-on-write + Proxy = ?
一.简介 Immer (German for: always) is a tiny package that allows you to work with immutable state in a ...
- 【leetCode】485. 最大连续1的个数
给定一个二进制数组, 计算其中最大连续1的个数. 示例 1: 输入: [1,1,0,1,1,1]输出: 3解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.注意: 输入的数组只包含 ...