读懂React原理之调和与Fiber

2022-11-23
13715 分钟
...

一 引沿

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;              // 指向父级fiber
  this.child = null;               // 指向子级fiber
  this.sibling = null;             // 指向兄弟fiber 
  this.index = 0;                  // 索引

  this.ref = null;                 // ref指向,ref函数,或者ref对象。

  this.pendingProps = pendingProps;// 在一次更新中,代表element创建
  this.memoizedProps = null;       // 记录上一次更新完毕后的props
  this.updateQueue = null;         // 类组件存放setState更新队列,函数组件存放
  this.memoizedState = null;       // 类组件保存state信息,函数组件保存hooks信息,dom元素为null
  this.dependencies = null;        // context或是时间的依赖项

  this.mode = mode;                //描述fiber树的模式,比如 ConcurrentMode 模式

  this.effectTag = NoEffect;       // effect标签,用于收集effectList
  this.nextEffect = null;          // 指向下一个effect

  this.firstEffect = null;         // 第一个effect
  this.lastEffect = null;          // 最后一个effect

  this.expirationTime = NoWork;    // 通过不同过期时间,判断任务是否过期, 在v17版本用lane表示。

  this.alternate = null;           //双缓存树,指向缓存的fiber。更新阶段,两颗树互相交替。
}` 

![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

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 元素 比如 <div>
export const HostText = 6;                // 文本节点
export const Fragment = 7;                // 对应 <React.Fragment> 
export const Mode = 8;                    // 对应 <React.StrictMode> 
export const ContextConsumer = 9;         // 对应 <Context.Consumer>
export const ContextProvider = 10;        // 对应 <Context.Provider>
export const ForwardRef = 11;             // 对应 React.ForwardRef
export const Profiler = 12;               // 对应 <Profiler/ >
export const SuspenseComponent = 13;      // 对应 <Suspense>
export const MemoComponent = 14;          // 对应 React.memo 返回的组件`

比如元素结构如下:

`export default class Parent extends React.Component{
   render(){
     return <div>
       <h1>hello,world</h1>
       <Child />
     </div>
   }
}

function Child() {
  return <p>child</p>
}`

对应的Filber结构如下:

alt 属性文本

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

四 实现调和的过程

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

`const jsx = (
  <div className="border">
    <h1>hello</h1>
    <a href="https://www.reactjs.org/">React</a>
  </div>
)

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 }`

参考React实战视频讲解:进入学习

2. render阶段

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

ReactFiberWorkloop.js

`let wipRoot = null // work in progress
let nextUnitOfwork = null // 下一个fiber节点

export function scheduleUpdateOnFiber(fiber) {
  wipRoot = fiber
  nextUnitOfwork = fiber
}

function workLoop(IdleDeadline) {
  while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {
    nextUnitOfwork = performUnitOfWork(nextUnitOfwork)
  }
}

function performUnitOfWork() {}

requestIdleCallback(workLoop)` 

![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

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

performUnitOfWork 包括两个阶段:

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

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

`import { updateHostComponent } from './ReactFiberReconciler'
function performUnitOfWork(wip) {
  // 1. 更新wip
  const { type } = wip
  if (isStr(type)) {
    // type是string,更新普通元素节点
    updateHostComponent(wip)
  } else if (isFn(type)) {
    // ...
  }

  // 2. 返回下一个要更新的任务 深度优先遍历
  if (wip.child) {
    return wip.child
  }
  let next = wip
  while(next) {
    if (next.sibling) {
      return next.sibling
    }
    next = next.return
  }
  return null
}` 

![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

根据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 = null
  for(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];
    }
  });
}` 

![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

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)
  }

  // commit
  if (!nextUnitOfwork && wipRoot) {
    commitRoot();
  }
}

function commitRoot() {
  commitWorker(wipRoot.child)
  wipRoot = null;
}

function commitWorker(wip) {
  if (!wip) {
    return
  }
  // 1. 提交自己
  const { stateNode } = wip
  let parentNode = wip.return.stateNode
  if (stateNode) {
    parentNode.appendChild(stateNode);
  }

  // 2. 提交子节点
  commitWorker(wip.child);

  // 3. 提交兄弟节点
  commitWorker(wip.sibling);
}` 

![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

五 总结

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

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
react2025-12-02
useEffect与useLayoutEffect对比
在 React Hooks 中, 和 都用于处理副作用逻辑,例如订阅、事件监听、DOM 操作等。但它们有着本质性的执行时机差异,这影响到页面渲染、布局抖动、性能表现等关键点。 1\. 执行时机的核心区别 在 React 的渲染流程中: 1. 渲染(Render phase) React 根据 state/props 计算 UI,生成虚拟 DOM,不访问真实 DOM。 2. 提交(Commit phase) React 将虚拟 DOM 变...
学习
react2025-08-03
在 React 中实现倒计时功能会有什么坑
倒计时 倒计时是一个非常常见的业务场景,但是在 React 中实现起来,却不算简单。 首先我们来看这段倒计时代码,它能否正常执行? 来看看实际表现效果 可以看到计时器在不断执行,但是 的值却没有变。 这是一个很经典的问题:React 闭包陷阱 。 我们来详细分析下: 的依赖数组 是空的,这意味着 effect 只会在组件挂载时执行一次,而不会在 状态更新时重新执行。所以 回调函数中捕获的 值始终是初始值 。 这个问题解决起来也很简单,有...
学习面试
react2025-07-30
React性能优化三剑客:memo、useMemo和useCallback详解
前言 在React开发中,性能优化是一个永恒的话题。今天我们就来深入探讨React提供的三个重要性能优化工具: 、 和 ,它们如何帮助我们构建更高效的React应用。 1\. 为什么需要性能优化? 🤔 React的核心机制是当组件的state或props发生变化时,组件会重新渲染。但有时这种重新渲染是不必要的: 父组件更新导致所有子组件重新渲染 ,即使子组件的props没有变化 复杂计算在每次渲染时重复执行 ,消耗大量资源 函数引用在...
学习面试
react2025-06-05
React 中 useDeferredValue 和 startTransition 的核心区别与使用场景
React 中 和 ,两者都用于优化性能,但适用场景和实现方式不同。 核心概念解析 1\. useDeferredValue:延迟值更新 作用 :告诉 React 延迟更新某个值 ,直到所有高优先级渲染任务完成。 适用场景 :当某个值的更新会触发计算密集型渲染(如复杂列表、图表),但需要优先保证其他高优先级 UI(如输入框)的响应。 机制 : 保持旧值显示,后台计算新值。 计算完成后,用新值更新 UI,期间允许用户继续交互。 示例 :...
学习
react2024-12-18
Antd 样式覆盖
&nbsp; 目前作者所在的业务正在升级 Antd5.0,不得不说 Antd5.0 真的太香了,但是由于团队的 UE 规范进行了大改版,Antd5.0 所有的 必须对齐最新的规范,因此就需要对 Antd5.0 组件做主题定制。 现状 目前针对项目中使用的 antd 高频组件进行了梳理,并且根据规范基于 design token 对这些高频组件进行样式定制,对于主题色、圆角、边框、字体等用户 高的主题都已经满足团队规范。 虽然 Desig...
学习
react2024-12-06
React 19 终于发布,一大波新功能正式升级
这篇文章主要介绍了 React 19 的新功能,包括 Actions 自动处理数据突变相关状态,新增的 useActionState、useFormStatus、useOptimistic 等 hook,新的 use API 读取渲染资源,新的 React DOM 静态 API 生成静态站点,以及 React 服务器组件中的 RSC 和 RSA 等内容。同时提到后续会分享针对旧版功能的优化和改良。 关联问题: React 19 性能如何...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录