Fiber学习
生成virtual DOM
树
bable转换jsx的结果
let style = { border: "1px solid orange", margin: "5px" }
let element = (
<div id="A1" style={style}> A1
<div id="B1" style={style}> B1
<div id="C1" style={style}>C1</div>
<div id="C2" style={style}>C2</div>
</div>
<div id="B2" style={style}>B2</div>
</div>
)
//babel解析结果:
// React.createElement("div", { id: "A1", style: style },
// " A1",
// React.createElement("div", { id: "B1", style: style },
// " B1",
// React.createElement("div", { id: "C1", style: style }, "C1"),
// React.createElement("div", { id: "C2", style: style }, "C2")
// ),
// React.createElement("div", { id: "B2", style: style }, "B2")
// );
实现createElment
import { ELEMENT_TEXT } from './constants'
function createElement(type, props, ...children) {
delete props.__source;
delete props.__self;
return {
type,
props: {
...props,
children: children.map(child => {
/* 文本节点特殊处理 */
return typeof child === 'object' ? child : {
type: ELEMENT_TEXT,
props: { text: child, children: [] }
}
})
}
}
}
React.createElement处理过后的virtual DOM
实现render方法
render使用
ReactDOM.render(
element,
document.getElementById('root')
);
react-dom入口文件
import { TAG_ROOT } from "../react/constants"
import scheduleRoot from './schedule'
function render(element, container) {
/* 根fiber */
let rootFiber = {
tag: TAG_ROOT,
stateNode: container,
props: { children: [element] }
};
scheduleRoot(rootFiber);
}
export default { render}
1. 初始化
let wrokProgressRoot = null;//记录根节点
let nextUnitOfWork = null;//记录当前工作单元
function scheduleRoot(rootFiber) {
wrokProgressRoot = rootFiber;
nextUnitOfWork = rootFiber;
}
2. 工作循环
requestIdleCallback
作用
实现在浏览器空闲时运行workLoop
,若超过500ms之不管是否空闲都运行(60帧显示器每16.6ms刷新一次,存在空闲时间则执行performUnitOfwork
,一直到达500ms无空闲时间则强制执行)
// 工作循环
function workLoop(deadline) {
// 时间片未到
if ((deadline.timeout || deadline.timeRemaining() > 0) && nextUnitOfWork) {
/* 执行工作单元,返回下一单元 */
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
/* 还有工作单元未执行 */
if (!nextUnitOfWork && wrokProgressRoot) {
console.log("Render 完成");
// 生成DOM
commitRoot();
}
requestIdleCallback(workLoop, { timeout: 500 });
}
requestIdleCallback(workLoop, { timeout: 500 });
2.1 performUnitOfWork执行工作单元
function performUnitOfWork(currentFiber) {
//构建
beginWork(currentFiber);
/* 子元素 */
if (currentFiber.child) {
return currentFiber.child;
}
/* 没有子元素释放自己并往上找 */
while (currentFiber) {
/* fiber子元素全部完成,将自身合并到副作用链 */
completeUnitOfWork(currentFiber);
if (currentFiber.sibling) {
return currentFiber.sibling;
}
/* 往上找 */
currentFiber = currentFiber.return;
}
}
2.1.1 beginWork分类构建
function beginWork(currentFiber) {
//根元素
if (currentFiber.tag === TAG_ROOT) {
upDateRoot(currentFiber);
}
//原生节点
if (currentFiber.tag === TAG_HOST) {
upDateHost(currentFiber);
}
//文本节点
if (currentFiber.tag === TAG_TEXT) {
upDateText(currentFiber);
}
}
/* 更新根元素 */
function upDateRoot(currentFiber) {
/* 构建子元素 */
let newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
/* 更新原生元素 */
function upDateHost(currentFiber) {
/* 创建DOM */
if (!currentFiber.stateNode) {
currentFiber.stateNode = createDOM(currentFiber);
}
/* 获取并构建子元素 */
let newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
/* 更新文本元素 */
function upDateText(currentFiber) {
if (!currentFiber.stateNode) {
currentFiber.stateNode = createDOM(currentFiber);
}
}
createDOM方法创建Dom,设置属性
/* 创建DOM */
function createDOM(currentFiber) {
if (currentFiber.tag === TAG_TEXT) {
/* 直接船舰文本节点 */
return document.createTextNode(currentFiber.props.text);
}
else if (currentFiber.tag === TAG_HOST) {
let stateNode = document.createElement(currentFiber.type);//<div></div>
setProps(stateNode, {}, currentFiber.props);
//<div id="A1" style="border: 1px solid orange; margin: 5px;"></div>
return stateNode;
}
}
reconcileChildren遍历子节点并记录父子兄弟fiber关系
/* 创建子元素fiber并连接到父元素上 */
function reconcileChildren(returnFiber, newChildren) {
let newChildIndex = 0,prevSibling = null;
//遍历returnFiber的子节点
while (newChildIndex < newChildren.length) {
let newChild = newChildren[newChildIndex];
/* 标识文本和原生组件 */
let tag;
if (newChild.type === ELEMENT_TEXT) {
tag = TAG_TEXT;
} else if (typeof newChild.type === "string") {
tag = TAG_HOST;
}
/* 创建Fiber */
let newFiber = {
tag,
type: newChild.type,
props: newChild.props,
stateNode: null,
return: returnFiber,//父节点
effectTag: PLACEMENT,//操作
nextEffect: null//下一个节点
}
/* 连接Fiber,第一个子元素作为儿子,其他作为兄弟连接 */
if (newChildIndex === 0) {
returnFiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
newChildIndex++;
}
}
2.1.2 completeUnitOfWork构建副作用链
function completeUnitOfWork(currentFiber) {
/* 获取父节点 */
let returnFiber = currentFiber.return;
if (returnFiber) {
/* 将自己连接到父元素 */
if (!returnFiber.firstEffect) {
returnFiber.firstEffect = currentFiber.firstEffect;
}
if (currentFiber.lastEffect) {
/* 将当前fiber头部接到父fiber尾部 */
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
}
/* 当前尾部作为最终尾部 */
returnFiber.lastEffect = currentFiber.lastEffect;
}
/* 连接子元素 */
if (currentFiber.effectTag === PLACEMENT) {
/* 当前元素尾部有元素才会连接 */
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber;
} else {
returnFiber.firstEffect = currentFiber;
}
/* 更新尾巴 */
returnFiber.lastEffect = currentFiber;
}
}
}
2.2 commit挂载DOM
function commitWork(currentFiber) {
/* 获取父元素 */
let returnDom = currentFiber.return.stateNode;
/* 副作用类型 */
if (currentFiber.effectTag === PLACEMENT) {
returnDom.appendChild(currentFiber.stateNode);
}
/* 去除副作用 */
currentFiber.effectTag = null;
}
function commitRoot() {
/* 获取链表头 */
let currentFiber = wrokProgressRoot.firstEffect;
while (currentFiber) {
commitWork(currentFiber);
currentFiber = currentFiber.nextEffect;
}
wrokProgressRoot = null;
}
总结
ReactDOM的render方法首先将虚拟DOM树进行扩充,记录节点的孩子和兄弟,然后将副作用节点按照自底向上的顺序记录在一个链表中,commit时实现从里到外改变DOM。