1. 为什么需要fiber?
- 对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局,动画等周期性任务无法立即得到处理,造成视觉上的卡顿,影响用户体验。
- fiber进行任务的分解,解决了上面的问题。
- 然后增量渲染(把渲染任务拆分成块,匀到多帧)。
- 更新时能够暂停,终止,复用渲染任务。
- 给不同类型的更新赋予优先级(onClick,onChange等在react中属于合成事件,react源码中会对这些事件赋予优先级)。
- 并发方面新的基础能力。
- 更流畅。
// 不用fiber的时候,处理childre,数组遍历的缺点就是完全跟顺序相关,必须要前面一个执行完,才会接着执行嘛。就阻塞了主进程,后面如果有优先级更高的就只能等着呀!!!
function reconcileChildren_old(children, node) {
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (Array.isArray(child)) {
for (let j = 0; j < child.length; j++) {
render(child[j], node);
}
} else {
render(child, node);
}
}
}
2. 什么是fiber?
fiber是指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个。
3. fiber相关知识点
- react底层有一套基于window.requestIdleCallback()的fiber实现机制,也就是schedule(react中requestIdleCallback的hack在 react/packages/scheduler/src/forks/SchedulerHostConfig.default.js),这个后面再说啦。
- window.requestIdleCallback(callback,options(可选参数))方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主 事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先 进先调用的顺序执行,然而,如果回调函数指定了执行超时时间 timeout ,则有可能为了在超时前执行 函数而打乱执行顺序。你可以在空闲回调函数中调用 requestIdleCallback() ,以便在下一次通过事件循环之前调度另一个回调。
4. 实现fiber
- Fiber 是 React 16 中新的协调引擎。它的主要目的是使 Virtual DOM 可以进行增量式渲染。 一个更新过程可能被打断。
- 所以React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段 Reconciliation Phase和第二阶段Commit Phase。
- fiber是链表的结构进行存储的。(注意,子的return一定是父fiber,但是父的child不一定是这个子fiber,因为这个子fiber不一定是父的第一个firber呀!!!)
- 实现代码 定义类型 --- ./const
export const TEXT = "TEXT";
export const PLACEMENT = "PLACEMENT";
export const UPDATE = "UPDATE";
export const DELETION = "DELETION";
fiber实现新增---./react-dom.js
import {TEXT, PLACEMENT} from "./const";
// 下一个单元任务 fiber
let nextUnitOfWork = null;
// work in progress fiber root (正在执行的根fiber)
let wipRoot = null;
/**
* fiber架构
* type: 标记类型
* key: 标记当前层级下的唯一性
* child : 第一个子元素 fiber
* sibling : 下一个兄弟元素 fiber
* return: 父fiber
* node: 真实dom节点
* props:属性值
* base: 上次的节点 fiber
* effectTag: 标记要执行的操作类型(删除、插入、更新)
*/
// ! vnode 虚拟dom对象
// ! node 真实dom
function render(vnode, container) {
// 初始wipRoot的值
wipRoot = {
node: container,
props: {
children: [vnode]
}
};
nextUnitOfWork = wipRoot;
}
// 创建node
function createNode(vnode) {
const {type, props} = vnode;
let node = null;
// 判断节点类型
if (type === TEXT) {
node = document.createTextNode("");
} else if (typeof type === "string") {
node = document.createElement(type);
} else if (typeof type === "function") {
// 判断是函数组件还是类组件
node = type.prototype.isReactComponent
? updateClassComponent(vnode)
: updateFunctionComponent(vnode);
} else {
node = document.createDocumentFragment();
}
// 把props.children遍历,转成真实dom节点 ,再插入node
// reconcileChildren(props.children, node);
// 更新属性节点
updateNode(node, props);
return node;
}
// 类组件
function updateClassComponent(fiber) {
const {type, props} = fiber;
const cmp = new type(props);
let children = [cmp.render()];
reconcileChildren(fiber, children);
}
// 函数组件
function updateFunctionComponent(fiber) {
const {type, props} = fiber;
const children = [type(props)];
reconcileChildren(fiber, children);
}
// 更新属性值,如className、nodeValue等
function updateNode(node, nextVal) {
Object.keys(nextVal)
.filter(k => k !== "children")
.forEach(k => {
node[k] = nextVal[k];
});
}
// workInProgressFiber Fiber ->child->sibling
// children 数组
function reconcileChildren(workInProgressFiber, children) {
// 构建fiber架构
let prevSlibling = null;
for (let i = 0; i < children.length; i++) {
let child = children[i];
// 现在只考虑初次渲染
// 创建一个新的fiber
let newFiber = {
type: child.type,
props: child.props,
node: null,
base: null,
return: workInProgressFiber,
effectTag: PLACEMENT
};
// 形成一个链表结构
if (i === 0) {
workInProgressFiber.child = newFiber;
} else {
prevSlibling.sibling = newFiber;
}
prevSlibling = newFiber;
}
}
function updateHostComponent(fiber) {
if (!fiber.node) {
fiber.node = createNode(fiber);
}
// 协调子元素
const {children} = fiber.props;
reconcileChildren(fiber, children);
}
function performUnitOfWork(fiber) {
//执行当前任务 更新当前fiber节点
const {type} = fiber;
if (typeof type === "function") {
// class function
type.prototype.isReactComponent
? updateClassComponent(fiber)
: updateFunctionComponent(fiber);
} else {
// 原生标签
updateHostComponent(fiber);
}
//获取下一个子任务(fiber)
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
// 找到兄弟
if (nextFiber.sibling) {
return nextFiber.sibling;
}
// 没有兄弟 往祖先上找
nextFiber = nextFiber.return;
}
}
function workLoop(deadline) {
// 有下一个任务 并且当前帧没有结束
// 这里的时间是模拟,源码当中用的过期时间,源码中的过期时间和时间单位相关内
while (nextUnitOfWork && deadline.timeRemaining() > 1) {
//执行当前任务
//获取下一个子任务(fiber)
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
if (!nextUnitOfWork && wipRoot) {
// 提交
commitRoot();
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
// ! 提交任务
function commitRoot() {
commitWorker(wipRoot.child);
wipRoot = null;
}
function commitWorker(fiber) {
if (!fiber) {
return;
}
// 找到parentNode,
// 找到最近的有node节点的祖先fiber
let parentNodeFiber = fiber.return;
while (!parentNodeFiber.node) {
parentNodeFiber = parentNodeFiber.return;
}
const parentNode = parentNodeFiber.node;
if (fiber.effectTag === PLACEMENT && fiber.node !== null) {
// 新增插入(dom父子关系插入)
parentNode.appendChild(fiber.node);
}
commitWorker(fiber.child);
commitWorker(fiber.sibling);
}
export default {render};