为什么需要fiber?
React Fiber 是针对就协调器重写的完全向后兼容的一个版本。React 的这种新的协调算法被称为 Fiber Reconciler。这个名字来自于 fiber,它经常被用来表示 DOM 树的节点。我们将在后面的章节中详细介绍 fiber。
Fiber 协调器的主要目标是增量渲染,更好更平滑地渲染 UI 动画和手势,以及用户互动的响应性。协调器还允许你将工作分为多个块,并将渲染工作分为多个帧。它还增加了为每个工作单元定义优先级的能力,以及暂停、重复使用和中止工作的能力。
React 的其他一些特性包括从一个渲染函数返回多个元素,支持更好的错误处理(我们可以使用 componentDidCatch 方法来获得更清晰的错误信息),以及 portals。
在计算新的渲染更新时,React 会多次回访主线程。因此,高优先级的工作可以跳过低优先级的工作。React 在内部为每个更新定义了优先级。
fiber之前的渲染方式
const example = (
<div id="0">
<div id="1"></div>
<div id="2"></div>
</div>
);
const render = (element, parentElement) => {
const dom =
typeof element === "object"
? document.createElement(element.type)
: document.createTextNode(element);
element.props &&
Object.keys(element.props)
.filter((e) => e !== "children")
.forEach((e) => {
dom[e] = element.props[e];
});
if (element.props?.children) {
if (Array.isArray(element.props.children)) {
element.children.forEach((e) => {
dom.appendChild(render(e, element));
});
} else {
dom.appendChild(render(element.props.children, element));
}
}
parentElement.appendChild(dom);
};
const root = document.getElementById("#root");
render(example, root);
Fiber
实现 createElement 方法 (手写自己的 React.js)
function createElement(type, config, children) {
const props = {};
const key = config.key ? config.key : null;
const ref = config.ref ? config.ref : null;
Object.keys(config)
.filter((k) => k !== "key" || "ref")
.forEach((k) => {
props[k] = config[k];
});
if (arguments.length === 3) props.children = [children];
else {
const arr = [];
for (let i = 2; i < arguments.length; i++) {
arr.push(arguments[i]);
}
props.children = arr;
}
return {
$$typeof: "REACT_TYPE",
type,
ref,
props,
key,
};
}
export default {
createElement,
};
实现 fiber (手写自己的 ReactDOM.js)
let workInProgressRoot, nextUnitOfWork;
export function startWorkLoop(rootFiber) {
workInProgressRoot = rootFiber;
nextUnitOfWork = workInProgressRoot;
window.requestIdleCallback(workLoop);
}
function workLoop(deadline) {
console.log("workLoop, start; workInProgressRoot: ", workInProgressRoot);
while (nextUnitOfWork && deadline.timeRemaining() > 0) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
workInProgressRoot ? commitRoot() : window.requestIdleCallback(workLoop);
}
function performUnitOfWork(fiber) {
console.log("performUnitOfWork: ", fiber.props.id);
beginWork(fiber);
if (fiber.children) return fiber.children;
while (fiber !== workInProgressRoot) {
completeUnitOfWork(fiber);
if (fiber.silbing) return fiber.silbing;
fiber = fiber.return;
}
}
function createDOM(fiber) {
console.log("createDOM: ", fiber.props.id);
if (fiber.type === "TEXT")
return document.createTextNode(fiber.props.nodeValue);
function isFunc(f) {
return typeof f.type !== "function" ? f : isFunc(f.type(f.props));
}
if (typeof fiber.type === "function") {
const f = isFunc(fiber.type(fiber.props));
return createDOM(f);
} else {
return document.createElement(fiber.type);
}
}
function beginWork(fiber) {
console.log("beginWork: ", fiber.props.id);
if (!fiber.stateNode) fiber.stateNode = createDOM(fiber);
fiber.props &&
Object.keys(fiber.props)
.filter((e) => e !== "children")
.forEach((k) => {
fiber.stateNode[k] = fiber.props[k];
});
let preFiber;
fiber.props.children &&
fiber.props.children.forEach((e, i) => {
const newFiber = {
type: e.type,
props: e.props,
return: fiber,
effectTag: "PLACEMENT",
};
if (i === 0) {
fiber.children = newFiber;
} else {
if (preFiber) preFiber.silbing = newFiber;
}
preFiber = newFiber;
});
}
function completeUnitOfWork(fiber) {
console.log("completeUnitOfWork: ", fiber.props.id);
const returnFiber = fiber.return;
if (returnFiber) {
if (!returnFiber.firstEffect) returnFiber.firstEffect = fiber.firstEffect;
if (fiber.lastEffect) {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = fiber.firstEffect;
}
returnFiber.lastEffect = fiber.lastEffect;
}
if (fiber.effectTag === "PLACEMENT") {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = fiber;
} else {
returnFiber.firstEffect = fiber;
}
returnFiber.lastEffect = fiber;
}
}
}
function commitRoot() {
console.log("commitRoot");
let currentFiber = workInProgressRoot.firstEffect;
while (currentFiber) {
if (currentFiber.effectTag === "PLACEMENT") {
currentFiber.return.stateNode.appendChild(currentFiber.stateNode);
}
currentFiber = currentFiber.nextEffect;
}
workInProgressRoot = null;
}
调用
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
#root {
width: 300px;
height: 300px;
background: #000;
}
#parent {
width: 200px;
height: 200px;
background: #7c7c7c;
}
#son {
width: 100px;
height: 100px;
background: #ffffff;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.js"></script>
</body>
</html>
index.js
import React from "./React.js";
import { startWorkLoop } from "./ReactDOM.js";
const element = React.createElement(
"div",
{
key: "0",
ref: "r",
id: "parent",
},
React.createElement("div", {
title: "son",
id: "son",
})
);
console.log(element);
const rootFiber = {
stateNode: document.querySelector("#root"),
type: "div",
props: {
id: "root",
children: [element],
},
};
startWorkLoop(rootFiber);
interface Fiber {
/**
* ⚛️ 节点的类型信息
*/
// 标记 Fiber 类型, 例如函数组件、类组件、宿主组件
tag: WorkTag;
// 节点元素类型, 是具体的类组件、函数组件、宿主组件(字符串)
type: any;
/**
* ⚛️ 结构信息
*/
return: Fiber | null;
child: Fiber | null;
sibling: Fiber | null;
// 子节点的唯一键, 即我们渲染列表传入的key属性
key: null | string;
/**
* ⚛️ 节点的状态
*/
// 节点实例(状态):
// 对于宿主组件,这里保存宿主组件的实例, 例如DOM节点。
// 对于类组件来说,这里保存类组件的实例
// 对于函数组件说,这里为空,因为函数组件没有实例
stateNode: any;
// 新的、待处理的props
pendingProps: any;
// 上一次渲染的props
memoizedProps: any; // The props used to create the output.
// 上一次渲染的组件状态
memoizedState: any;
/**
* ⚛️ 副作用
*/
// 当前节点的副作用类型,例如节点更新、删除、移动
effectTag: SideEffectTag;
// 和节点关系一样,React 同样使用链表来将所有有副作用的Fiber连接起来
nextEffect: Fiber | null;
/**
* ⚛️ 替身
* 指向旧树中的节点
*/
alternate: Fiber | null;
}
大家可以看看我的github,点点star
: )
作者整理的面试文档