协调-reconciliation
设计动力
调用React的render()方法会创建一颗由React元素组成的树,在下一次state、props或context更新时候,相同的render()方法会返回一颗不同的树。React需要基于这两颗树之间的差别来判断如何有效的更新UI以保证当前UI与最新的树保持同步
React提出了一套O(n)-n是树中元素的数量,启发式算法(diff算法):
1、两个不同类型的元素会产生出不同的树
2、开发者可以通过key
prop属性来暗示哪些子元素在不同的渲染下能保持稳定
diff算法-算法复杂度O(n)
diff策略
1、同级比较,Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计
2、拥有不同类型的两个组件将会生成不同的树形结构,例如:div->p,CompA->CompB
3、开发者可以通过key
prop属性来暗示哪些子元素在不同的渲染下能保持稳定
节点能够复用的三个条件:同级节点、key值相同、节点类型相同
diff过程
对比两个虚拟dom时会有三种操作:删除、替换和更新
注:vnode是现在的虚拟dom,newVnode是新虚拟dom
删除:newVnode不存在时
替换:vnode和newVnode类型不同或key不同时
更新:有相同类型和key时,但vnode和newVnode的属性不同时
fiber
为什么需要fiber
- 对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验
- 任务分解就是解决上面的问题
- 增量渲染(把渲染任务拆分成块,匀到多帧)
- 更新时能够暂停,终止,复用渲染任务
- 给不同的类型的更新赋予优先级
- 并发方面的基础能力
- 更流畅
什么是fiber
fiber是指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个fiber,它的数据结构类似于链表,有指针指向,表示vnode节点
实现fiber
window.requestIdleCallback(callback[, options])
此方法将在浏览器的空闲时段内调用函数队列。这使得开发者能够在事件循环上执行后台和优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout
,则有可能为了在超时前执行函数而打乱顺序
可以在空闲回调函数中调用requestIdleCallback()
,以便在下一次通过事件循环之前调度另一个回调
Fiber是React 16中新的协调引擎,它的主要目的是使Virtual DOM可以进行增量渲染 一个更新过程可能会被打断,所以React Fiber一个更新过程被分为两个阶段:
第一个阶段Reconciliation Phase
第二个阶段Commit Phase
有fiber的react-dom.js文件
/**
* vnode 虚拟DOM
* node 真实DOM
*/
// 根节点 fiber
let wipRoot = null;
function render(vnode, container) {
/* console.log("vnode", vnode);
// vnode->node
const node = createNode(vnode);
// node 插入到container中
container.appendChild(node); */
wipRoot = {
type: "div",
props: { children: { ...vnode } },
stateNode: container
};
nextUnitWork = wipRoot;
}
function isString(str) {
return typeof str === "string";
}
// 根据vnode,生成node
function createNode(workInProgress) {
let node = document.createElement(workInProgress.type);
updateNode(node, workInProgress.props);
return node;
}
// 更新属性
function updateNode(node, nextVal) {
Object.keys(nextVal)
// .filter(k => k !== "children")
.forEach(k => {
if (k === "children") {
if (isString(nextVal.children)) {
node.textContent = nextVal.children;
}
} else {
if (k === "style") {
for (let attr in nextVal.style) {
node.style[attr] = nextVal.style[attr];
}
} else {
node[k] = nextVal[k];
}
}
});
}
// 原生标签
function updateHostComponent(workInProgress) {
// 修身
// 构建真实dom节点
if (!workInProgress.stateNode) {
workInProgress.stateNode = createNode(workInProgress);
}
// 齐家
// 协调子节点
reconcileChildren(workInProgress, workInProgress.props.children);
console.log("workInProgress", workInProgress);
}
// 文本
function updateTextComponent(workInProgress) {
if (!workInProgress.stateNode) {
workInProgress.stateNode = document.createTextNode(workInProgress.props);
}
}
// 函数组件
function updateFunctionComponent(workInProgress) {
const { type, props } = workInProgress;
const children = type(props);
reconcileChildren(workInProgress, children);
}
// 类组件
function updateClassComponent(workInProgress) {
const { type, props } = workInProgress;
// 先实例化
const instance = new type(props);
const children = instance.render();
reconcileChildren(workInProgress, children);
}
// <>Fragment节点
function updateFragmentComponent(workInProgress) {
reconcileChildren(workInProgress, workInProgress.props.children);
}
// 最假的吧,最简单的也是协调
function reconcileChildren(workInProgress, children) {
if (isString(children)) {
return;
}
let newChildren = Array.isArray(children) ? children : [children];
let previousNewFiber = null;
for (let i = 0; i < newChildren.length; i++) {
let child = newChildren[i];
// 构建fiber节点
let newFiber = {
type: child.type, // 类型
props: { ...child.props }, // 属性
stateNode: null, // 如果是原生标签代表dom节点,如果是类组件就代表实例
child: null, // 第一个子节点fiber
sibling: null, // 下一个兄弟节点 fiber
return: workInProgress // 父节点
};
if (isString(child)) {
newFiber.props = child;
}
if (i === 0) {
// 第一个子fiber
workInProgress.child = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
/**
* fiber数据结构
* type
* props
* key
* stateNode 如果是原生标签代表dom节点,如果是类组件代表实例
* child 第一个子节点
* sibling 下一个兄弟节点
* return 指向父节点
*/
// workInProgress当前正在进行的中的fiber
function performNextUnitWork(workInProgress) {
// step1 执行当前任务
const { type } = workInProgress;
if (isString(type)) {
// 原生标签
updateHostComponent(workInProgress);
} else if (typeof type === "function") {
if (type.prototype.isReactComponent) {
// 类组件
updateClassComponent(workInProgress);
} else {
// 函数组件
updateFunctionComponent(workInProgress);
}
} else if (typeof type === "undefined") {
// 文本
updateTextComponent(workInProgress);
} else {
// Fragment
updateFragmentComponent(workInProgress);
}
// step2 并且返回下一个任务(深度优先遍历)
if (workInProgress.child) {
// 有孩子返回孩子
return workInProgress.child;
}
let nextFiber = workInProgress;
while (nextFiber) {
// 有兄弟返回兄弟,没有返回叔叔
if (nextFiber.sibling) {
return nextFiber.sibling;
}
// 叔叔就是爸爸的兄弟
nextFiber = nextFiber.return;
}
}
// 下一个要执行的任务
let nextUnitWork = null; // fiber
function workLoop(IdleDeadline) {
while (nextUnitWork && IdleDeadline.timeRemaining() > 1) {
// 执行当前任务,并且返回下一个任务
nextUnitWork = performNextUnitWork(nextUnitWork);
}
// 没有任务就提交
if (!nextUnitWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
function commitRoot() {
commitWorker(wipRoot.child);
wipRoot = null;
}
function commitWorker(workInProgress) {
if (!workInProgress) {
return;
}
// 更新自己
let parentNodeFiber = workInProgress.return;
while (!parentNodeFiber.stateNode) {
parentNodeFiber = parentNodeFiber.return;
}
let parentNode = parentNodeFiber.stateNode;
if (workInProgress.stateNode) {
parentNode.appendChild(workInProgress.stateNode);
}
// 更新子节点
commitWorker(workInProgress.child);
// 更新下一个兄弟节点
commitWorker(workInProgress.sibling);
}
// 在浏览器的空闲时间段内调用的函数排队
requestIdleCallback(workLoop);
// eslint-disable-next-line
export default { render };