手写 React 第 4 节 - 实现 Fiber
前言
在上一节【手写 React 第 3 节 - 实现函数组件/类组件】中实现了函数组件和类组件,本小节主要介绍 Fiber 的作用以及如何实现 Fiber。
1. Fiber 的作用
Fiber 是 React 16 之后才出来的,React 16 之前是没有 Fiber 的。Fiber 的作用主要是避免渲染中断出现异常以及控制页面渲染的优先权。
Fiber 的工作机制是将页面渲染的实现逻辑变成颗粒度更小的工作单元,并调用浏览器内置的 requestIdleCallback
方法。该方法会在浏览器空闲时间执行函数,使页面渲染减少卡顿,并实现渲染优先级。
2. Fiber 数据结构
function FiberNode(tag, pendingProps, key, mode) {
//节点类型
this.type = null;
//节点属性
this.props = props;
// Fiber对应的真实 DOM 节点
this.stateNode = null;
// 以下属性用于连接其他 Fiber 节点形成 Fiber 树。
// 指向父Fiber节点
this.return = null;
//指向第一个子Fiber节点
this.child = null;
//指向右边第一个兄弟Fiber节点
this.sibling = null;
}
import React from './react/index';
import ReactDOM from './react/react-dom';
import './index.css';
const App = (
<div>
<p>
<h1 className="border">Mini React</h1>
<span>fiber</span>
</p>
<div><span>fiber</span></div>
</div>
)
ReactDOM.render(App,document.getElementById('root'));
在以上树结构中,采用深度优先遍历,节点规则如下:
A 节点的 return(父节点) 为 null,child 为 B 节点,sibling 为 null。
B 节点的 return(父节点) 为 A 节点,child 为 D 节点,sibling 为 C 节点。
D 节点的 return(父节点) 为 B 节点,child 为 null,sibling 为 E 节点。
E 节点的 return(父节点) 为 B 节点,child 为 null,sibling 为 null。
C节点的 return(父节点) 为 A 节点,child 为 F 节点,sibling 为 null。
F 节点的 return(父节点) 为 C 节点,child 为 null,sibling 为 null。
通过以上遍历后会构成一个 Fiber 链表结构。
3. 实现 Fiber
- 在
render
中定义根节点rootFiber
,并将下一个单元工作nextUnitOfWork
指向rootFiber
。可以理解为定义一个 Fiber 链表结构的表头。
//1.定义链表结构的表头
function render(vnode,container) {
rootFiber = {
stateNode:container,
props:{
children:[vnode]
}
}
nextUnitOfWork = rootFiber;
}
- 开启 requestIdleCallback
//2.开启 requestIdleCallback
requestIdleCallback(workLoop);
- 进入工作循环
首先构建链表结构,然后将链表结构插入到真实 DOM 节点。
function workLoop(deadline) {
//3.1 构建 fiber 链表结构
while(nextUnitOfWork && deadline.timeRemaining()>1){
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
//3.2 构建完成之后,插入到真实 DOM
if(!nextUnitOfWork && rootFiber){
commitRoot();
}
}
3.1 构建 fiber 链表结构
首先构建当前 fiber 链表结构,再返回下一个 fiber
function performUnitOfWork(workInProcess) {
//3.1.1 更新当前 fiber 节点的链表结构
updateHostComponent(workInProcess);
//3.1.2 返回下一个 fiber 节点
return nextFiberWork(workInProcess);
}
3.1.1 更新当前 fiber 节点的链表结构
//3.1.1 更新当前 fiber 节点的链表结构
function updateHostComponent(workInProcess){
if(!workInProcess.stateNode){
workInProcess.stateNode = createNode(workInProcess);
}
reconcileChildren(workInProcess,workInProcess.props.children)
}
//创建真实 DOM 节点
function createNode(vnode,container) {
const {type,props} = vnode;
let node;
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,container):updateFunctionComponent(vnode,container)
}
reconcileChildren(vnode,props.children);
updateNode(node,props);
return node;
}
// 创建子节点的 fiber 结构
function reconcileChildren(fiber,children) {
let prevSibling = null;
for(let i = 0;i<children.length;i++){
let child = children[i];
let newFiber = {
type:child.type,
props:child.props,
stateNode:null,
return:fiber,
child:null,
sibling:null
}
if(i === 0){
fiber.child = newFiber;
}else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
}
}
//更新节点属性
function updateNode(node,props) {
for(let i in props){
if(i!='children'){
node[i] = props[i]
}
}
}
//更新类组件
function updateClassComponent(workInProcess,node) {
const {type,props} = workInProcess;
let instance = type(addProps(props));
return createNode(instance,node);
}
//更新函数组件
function updateFunctionComponent(workInProcess,node) {
const {type,props} = workInProcess;
let instance = new type(addProps(props));
return createNode(instance.render(),node);
}
//处理类组件和函数组件的属性
function addProps(props) {
let keys = Object.keys(props);
if(Array.isArray(keys) && keys.length>0){
let newProps = {},isProps;
keys.map(key=>{
if(key!='children'){
isProps = true;
newProps[key] = props[key];
}
})
if(isProps){
return newProps;
}
}
}
3.1.2 返回下一个 fiber 节点
//3.1.2 返回下一个 fiber 节点
function nextFiberWork(workInProcess) {
if(workInProcess.child){
return workInProcess.child
};
let nextFiber = workInProcess;
while (nextFiber){
if(nextFiber.sibling){
return nextFiber.sibling
}else {
nextFiber = nextFiber.return;
}
}
}
3.2 构建完成之后,插入到真实 DOM
//3.2 构建完成之后,插入到真实 DOM
function commitRoot() {
commitWorker(rootFiber.child);
rootFiber = null;
}
function commitWorker(fiber){
if(!fiber){
return;
}
//获取 fiber 父节点
let parentNodeFiber = fiber.return;
while(!parentNodeFiber.stateNode){
parentNodeFiber.stateNode = parentNodeFiber.return;
}
//获取真实 DOM 父节点
let parentNode = parentNodeFiber.stateNode;
if(fiber.stateNode){
//将当前真实节点插入到父节点
parentNode.appendChild(fiber.stateNode);
}
commitWorker(fiber.child);
commitWorker(fiber.sibling);
}