前言
本文基于建造属于你的react 这篇文章整理出来React的简单实现过程和一些核心概念,具体的细节和实现过程请参考原文。
React渲染过程中有两个重要的对象
ReactElement
对象和 fiber
对象ReactElement
对象
所有采用
jsx
语法书写的节点, 都会被编译器转换, 最终会以React.createElement(...)
的方式, 创建出来一个与之对应的ReactElement
对象。ReactElement
对象可以理解为jsx
语法的翻译。fiber
对象
fiber
对象是通过ReactElement
对象进行创建的, 多个fiber
对象构成了一棵fiber
树, fiber
树
是构造DOM树
的数据模型, fiber树
的任何改动, 最后都可以体现到DOM
上。为什么要引入
fiber
树呢,是因为如果直接用ReactElement
对象渲染页面的话,只能一次递归处理完,这样浏览器呈现卡死的状态,但是使用fiber
树渲染页面可以随时中断,将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。ReactElement
对象
jsx 转换为原生 js 需要通过一些诸如
babel
的编译工具。编译的过程把所有元素标签部分所有内容转换为 React.createElement(...)
函数,给函数传递元素标签名,标签上面的属性(prop),以及标签的子节点(children)。例如如下的
jsx
节点:const element = ( <div id="id"> <div className="className"> <h3 style="color: red"> <span>hello</span> </h3> </div> <header> <h2>World</h2> </header> </div> )
最终会被
React.createElement(...)
转化成如下的一个对象返回:{ "type": "div", "props": { "id": "id", "children": [ { "type": "div", "props": { "className": "className", "children": [ { "type": "h3", "props": { "style": "color: red", "children": [ { "type": "span", "props": { "children": [ { "type": "TEXT_ELEMENT", "props": { "nodeValue": "hello", "children": [] } } ] } } ] } } ] } }, { "type": "header", "props": { "children": [ { "type": "h2", "props": { "children": [ { "type": "TEXT_ELEMENT", "props": { "nodeValue": "World", "children": [] } } ] } } ] } } ] } }
fiber
对象
当 dom tree 很大的情况下,直接用
ReactElement
对象渲染页面,页面上会是长时间的卡住状态,无法进行用户输入等交互操作。可分为以下步骤解决上述问题:
- 允许中断渲染工作,如果有优先级更高的工作插入,则暂时中断浏览器渲染,待完成该工作后,恢复浏览器渲染;
- 将渲染工作进行分解,分解成一个个小单元;
步骤1可以使用浏览器的
requestIdleCallback
API解决允许中断渲染工作的问题,步骤2就需要引入feiber
对象处理了。假设需要渲染上述
ReactElement
对象,生成的 fiber
树如图:
fiber
树的目标是非常容易找到下一个单元工作,这也是为什么每一个 fiber
节点都有指向第一个节点和相邻节点以及父节点的链接。当我们完成在 fiber
上面的工作后,fiber
拥有 child
属性可以直接指向下一个需要进行工作的 fiber
节点。当
fiber
节点没有child
也没兄弟节点时,我们去他们的叔叔(父节点的兄弟节点)节点,如果fiber
的父节点也没有兄弟节点,我们继续往上找父节点的兄弟节点直到到根节点。当我们到根节点的时候,也意味着在这一次render
我们完成了所有的工作。具体的渲染流程
了解了两个最重要的对象和
requestIdleCallback
API后,React内部的渲染逻辑可以简单抽象为这样:
按照这个流程其实React第一次渲染时并不快,因为要额外生成fiber树,但是依靠fiber树这个数据结构,React更新是很快的,因为React保留了上次渲染完的fiber树。
State的简单处理
可以将State的信息挂在到当前的fiber对象中,这样每次SetState时,重新触发一次
workloop
就可以了。后记
这里只是针对建造属于你的react 这篇文章做了简单的总结,具体的实现细节还是建议先把建造属于你的react 这篇文章过一遍。