读懂React原理之调和与Fiber
创始人
2025-05-28 06:04:42
0

一 引沿

Fiber 架构是React16中引入的新概念,目的就是解决大型 React 应用卡顿,React在遍历更新每一个节点的时候都不是用的真实DOM,都是采用虚拟DOM,所以可以理解成fiber就是React的虚拟DOM,更新Fiber的过程叫做调和,每一个fiber都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间expirationTime,来判断是否还有空间时间执行更新,如果没有时间更新,就要把主动权交给浏览器去渲染,做一些动画,重排( reflow ),重绘 repaints 之类的事情,这样就能给用户感觉不是很卡。

二 什么是调和

调和是一种算法,就是React对比新老虚拟DOM的过程,以决定需要更新哪一部分。

三 什么是Filber

Fiber的目的是为了让React充分利用调度,以便做到如下几点:

  • 暂停工作,稍后再回来
  • 优先考虑不同类型的工作
  • 重用以前完成的工作
  • 如果不再需要,则中止工作

为了实现上面的要求,我们需要把任务拆分成一个个可执行的单元,这些可执行的单元就叫做一个Fiber,一个Fiber就代表一个可执行的单元。

一个Fiber就是一个普通的JS对象,包含一些组件的相关信息。

function FiberNode(){this.tag = tag;                  // fiber 标签 证明是什么类型fiber。this.key = key;                  // key调和子节点时候用到。 this.type = null;                // dom元素是对应的元素类型,比如div,组件指向组件对应的类或者函数。  this.stateNode = null;           // 指向对应的真实dom元素,类组件指向组件实例,可以被ref获取。this.return = null;              // 指向父级fiberthis.child = null;               // 指向子级fiberthis.sibling = null;             // 指向兄弟fiber this.index = 0;                  // 索引this.ref = null;                 // ref指向,ref函数,或者ref对象。this.pendingProps = pendingProps;// 在一次更新中,代表element创建this.memoizedProps = null;       // 记录上一次更新完毕后的propsthis.updateQueue = null;         // 类组件存放setState更新队列,函数组件存放this.memoizedState = null;       // 类组件保存state信息,函数组件保存hooks信息,dom元素为nullthis.dependencies = null;        // context或是时间的依赖项this.mode = mode;                //描述fiber树的模式,比如 ConcurrentMode 模式this.effectTag = NoEffect;       // effect标签,用于收集effectListthis.nextEffect = null;          // 指向下一个effectthis.firstEffect = null;         // 第一个effectthis.lastEffect = null;          // 最后一个effectthis.expirationTime = NoWork;    // 通过不同过期时间,判断任务是否过期, 在v17版本用lane表示。this.alternate = null;           //双缓存树,指向缓存的fiber。更新阶段,两颗树互相交替。
}

type 就是react的元素类型

export const FunctionComponent = 0;       // 对应函数组件
export const ClassComponent = 1;          // 对应的类组件
export const IndeterminateComponent = 2;  // 初始化的时候不知道是函数组件还是类组件 
export const HostRoot = 3;                // Root Fiber 可以理解为跟元素 , 通过reactDom.render()产生的根元素
export const HostPortal = 4;              // 对应  ReactDOM.createPortal 产生的 Portal 
export const HostComponent = 5;           // dom 元素 比如 
export const HostText = 6; // 文本节点 export const Fragment = 7; // 对应 export const Mode = 8; // 对应 export const ContextConsumer = 9; // 对应 export const ContextProvider = 10; // 对应 export const ForwardRef = 11; // 对应 React.ForwardRef export const Profiler = 12; // 对应 export const SuspenseComponent = 13; // 对应 export const MemoComponent = 14; // 对应 React.memo 返回的组件

比如元素结构如下:

export default class Parent extends React.Component{render(){return 

hello,world

} }function Child() {return

child

}

对应的Filber结构如下:

alt 属性文本

有了上面的概念后我们就自己实现一个Fiber的更新机制

四 实现调和的过程

我们通过渲染一段jsx来说明React的调和过程,也就是我们要手写实现ReactDOM.render()

const jsx = (

hello

React
)ReactDOM.render(jsx,document.getElementById('root') );

1. 创建FiberRoot

react-dom.js

function createFiberRoot(element, container){return {type: container.nodeName.toLocaleLowerCase(),props: { children: element },stateNode: container}
}function render(element, container) {const FibreRoot = createFiberRoot(element, container)scheduleUpdateOnFiber(FibreRoot)
}
export default { render }

2. render阶段

调和的核心是render和commit,本文不讲调度过程,我们会简单的用requestIdleCallback代替React的调度过程。

ReactFiberWorkloop.js

let wipRoot = null // work in progress
let nextUnitOfwork = null // 下一个fiber节点export function scheduleUpdateOnFiber(fiber) {wipRoot = fibernextUnitOfwork = fiber
}function workLoop(IdleDeadline) {while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {nextUnitOfwork = performUnitOfWork(nextUnitOfwork)}
}function performUnitOfWork() {}requestIdleCallback(workLoop)

每一个 fiber 可以看作一个执行的单元,在调和过程中,每一个发生更新的 fiber 都会作为一次 workInProgress 。那么 workLoop 就是执行每一个单元的调度器,如果渲染没有被中断,那么 workLoop 会遍历一遍 fiber 树

performUnitOfWork 包括两个阶段:

  1. 是向下调和的过程,就是由 fiberRoot 按照 child 指针逐层向下调和,期间会执行函数组件,实例类组件,diff 调和子节点
  2. 是向上归并的过程,如果有兄弟节点,会返回 sibling兄弟,没有返回 return 父级,一直返回到 fiebrRoot

这么一上一下,构成了整个 fiber 树的调和。

参考 前端进阶面试题详细解答

import { updateHostComponent } from './ReactFiberReconciler'
function performUnitOfWork(wip) {// 1. 更新wipconst { type } = wipif (isStr(type)) {// type是string,更新普通元素节点updateHostComponent(wip)} else if (isFn(type)) {// ...}// 2. 返回下一个要更新的任务 深度优先遍历if (wip.child) {return wip.child}let next = wipwhile(next) {if (next.sibling) {return next.sibling}next = next.return}return null
}

根据type类型区分是FunctionComponent/ClassComponent/HostComponent/…
本文中只处理HostComponent类型,其他类型的处理可以看文末的完整代码链接。

ReactFiberReconciler.js

import { createFiber } from './createFiber'export function updateHostComponent(wip) {if (!wip.stateNode) {wip.stateNode = document.createElement(wip.type);updateNode(wip.stateNode, wip.props);}// 调和子节点reconcileChildren(wip, wip.props.children);
}function reconcileChildren(returnFiber, children) {if (isStr(children)) {return}const newChildren = isArray(children) ? children : [children];let previousNewFiber = nullfor(let i = 0; i < newChildren.length; i++) {const newChild = newChildren[i];const newFiber = createFiber(newChild, returnFiber)if (previousNewFiber === null) {returnFiber.child = newFiber} else {previousNewFiber.sibling = newFiber}previousNewFiber = newFiber}
}function updateNode(node, nextVal) {Object.keys(nextVal).forEach((k) => {if (k === "children") {if (isStringOrNumber(nextVal[k])) {node.textContent = nextVal[k];}} else {node[k] = nextVal[k];}});
}

createFiber.js

export function createFiber(vnode, returnFiber) {const newFiber = {type: vnode.type,   // 标记节点类型key: vnode.key,     // 标记节点在当前层级下的唯一性props: vnode.props, // 属性stateNode: null,    // 如果组件是原生标签则是dom节点,如果是类组件则是类实例child: null,        // 第一个子节点return: returnFiber,// 父节点sibling: null,      // 下一个兄弟节点};return newFiber;
}

至此已经完成了render阶段,下面是commit阶段,commit阶段就是依据Fiber结构操作DOM

function workLoop(IdleDeadline) {while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {nextUnitOfwork = performUnitOfWork(nextUnitOfwork)}// commitif (!nextUnitOfwork && wipRoot) {commitRoot();}
}function commitRoot() {commitWorker(wipRoot.child)wipRoot = null;
}function commitWorker(wip) {if (!wip) {return}// 1. 提交自己const { stateNode } = wiplet parentNode = wip.return.stateNodeif (stateNode) {parentNode.appendChild(stateNode);}// 2. 提交子节点commitWorker(wip.child);// 3. 提交兄弟节点commitWorker(wip.sibling);
}

五 总结

  • Fiber结构,Fiber的生成过程。
  • 调和过程,以及 render 和 commit 两大阶段。

相关内容

热门资讯

南京亲子游新宠:溪谷乐园熊乐园... 在南京河西天街的角落里,一个全新的童话世界正在悄然绽放。没错,就是那个被众多家长誉为宝藏遛娃地的溪谷...
黄金股早盘走低,黄金股ETF跌... 黄金股早盘走低,中金黄金、招金矿业、山东黄金跌超5%。 受盘面影响,黄金股ETF跌超3%。 有分析...
长沙一小学校长带49名“小胖墩... 5月26日上午9点15分,长沙市阳高小学大课间时间到,操场逐渐热闹起来。校长杨志刚身穿运动T恤,手里...
收藏!垃圾分类知识get→ 为什么要进行垃圾分类? 垃圾分类是对垃圾收集处置传统方式的改革,是对垃圾进行有效处置的一种科学管理方...
社旗县大冯营镇张腰庄村驻村工作... 大象新闻记者 赵勇生 5月27日,在第77个“六一”国际儿童节来临之际,河南省税务局驻社旗县大冯营...
女子在美容店做隆胸手术后高烧,... 湖北黄冈蕲春县驻颜美容店老板骆女士为路女士实施隆胸手术,术后不久,路女士出现高烧、胸部肿胀等感染症状...
SpaceX调高美军“上网费” 在对伊朗战事中,美国的“低成本无人作战攻击系统”(卢卡斯)无人机打击效果显著。然而还没用几周,美国太...
美国锁定伊朗新目标,为恢复打击... 钛媒体App 5月27日消息,美国官员27日向媒体透露,美国国防部已拟定一份伊朗境内打击目标清单,以...
东方财富:发布2026-202... 3月19日消息,东方财富(300059.SZ)公告称,公司发布《未来三年股东分红回报规划(2026-...
美元指数日内跌近0.2%,最低... 3月19日消息,美元指数日内跌近0.2%,最低报99.98。(科股宝播报)