本文主要讲手写React中重要的几个部分,有助于建立对React源码的认知。
- CreateElement
相信大家一定对jsx不陌生
<div title="box">
<p>jsx</p>
<span>hhh</span>
</div>
React中的jsx其实就是一个语法糖,上述jsx经过babel翻译后是
React.createElement('div', {
title: 'box'},
React.createElement('p', {
}, 'jsx'),
React.createElement('span', {
}, 'hhh')
)
React.createElement: (type, props, ...children) => vDom
也就是说我们在写jsx实际上就是在写一个又一个嵌套的React.createElement。只是这样写太难维护了,所以使用了jsx。
React.createElement是干什么的?产生vDom的。
vDom(Virtual DOM),虚拟Dom节点,也就是自定义的一种数据结构,用来对应页面上真实的Dom节点。我们通过操纵vDom来操作真实的节点。
为什么使用vDom?
vDom比真实Dom轻量太多,真实Dom挂载的属性太多,很多根本用不上
可进一步支持跨平台,如RN
vDom结构如下
vDom: {
type,
props: {
...props,
children
}
}
我们自己写的createElement如下
// 将页面节点分为两类,text和非text
function createElement(type, props, ...children) {
return {
typp,
props: {
...props,
children: children.map(child => typeof child === 'object' ? child: createTextNode(child))
}
}
}
// 单独定义text vDom
function createTextNode(text) {
return {
type: 'TEXT',
props: {
nodeValue: text,
children: []
}
}
}
一切都很清楚了。我们写了一堆jsx以为描述了页面上真实dom的排布,实际上,babel将jsx翻译为了一堆的React.createElement,也就是说,最后我们写的jsx变成了一个vDom树
就拿最开始的例子
<div title="box">
<p>jsx</p>
<span>hhh</span>
</div>
=====》
{
type: 'div',
props: {
title: 'box',
children: [
{
type: 'p',
props: {
children: [{
type: 'TEXT', props: {
nodeValue: 'jsx', children: []}}]
}
},
{
type: 'span',
props: {
children: [{
type: 'TEXT', props: {
nodeValue: 'jsx', children: []}}]
}
}
]
}
}
最后我们得到了上面这个数据结构,它就是我们所描述的页面,下面,就是将这个数据结构渲染成真实dom
- fiber
根据上面的vDom树,直接渲染出真实页面很简单(递归createElement,appendChild),但是存在一个问题,每次render都会重绘整个页面,而这个过程是同步的,很耗时,会阻塞高优先级的任务,比如用户输入,动画之类。
React的解决办法是:
将长时间的同步任务拆分成多个小任务,从而让浏览器能够抽身去响应其他事件,等他空了再回来继续计算
这个是思路,实现可以使用requestIdleCallback和fiber
requestIdleCallback是一个浏览器实验性API,实现让浏览器空闲的时候来计算(React团队自己实现了这个API)
fiber是一种数据结构,可进行中断和回溯
具体来说,实现如下
nextUnitOfWork和workInProgressRoot是两个全局变量
nextUnitOfWork表示下一个访问的fiber节点
workInProgressRoot也叫wipRoot表示本次渲染的fiber树根节点
performUnitOfWork: (fiber) => fiber,传入要访问(工作)的fiber节点,返回下一个待处理的fiber节点
commitRoot: 提交所有vDom修改,一次性渲染到页面上
function workLoop(deadline) {
while (nextUnitOfWork && deadline.timeRemaining() > 1) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
if (!nextUnitOfWork && workInProgressRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
这段代码的意思是:
如果浏览器空闲,且存在待处理fiber,
就会处理该fiber并返回下一个待处理fiber
如果不存在待处理fiber了,而且本次要执行渲染,
就会将修改提交到页面上。
这个工作由浏览器调度,一直持续着。
那fiber到底长什么样呢?首先,说了fiber就是一种数据结构,不要害怕它
fiber我认为就是对vDom的一个扩展。按面向对象来说,可以认为fiber extends vDom
fiber: {
// 和vDom相同的属性
type,
props,
//----
dom, // 对应的真实dom节点
child, // 子指针,指向第一个儿子
sibling, // 兄弟指针,指向后一个相邻兄弟
return, // 父指针,每个儿子都有
alternate, // 老的fiber节点,用于diff
effectTag, // 标记,用于向页面提交更改,REPLACEMENT | UPDATE | DELETION
hooks // 该fiber上挂载的hook
}
后面三个属性可以先不看,相信你已经知道fiber长什么样了,就是一棵多了几个指向的树
下面分别来看一下提到的几个函数,performUnitOfWork,commitRoot
结语
React主要涉及这么几个方面:
React.createElement创建vDom
浏览器闲时调度
fiber
diff
hooks