记!为什么React中要在函数组件最顶层调用hook?以及什么是Fiber?

为了确保React能够正确地管理和跟踪每个组件的状态和副作用

解释:每个组件都会生成一个FiberNode(节点),而组件内使用的Hook会以链表的形式挂在FiberNode的memoizedState上面。当React重新渲染时,它会生成一个新的Fiber树,并根据之前的FiberNode获取之前的Hook,然后复制到新的FiberNode上,生成一个新的Hooks链表。React按照固定的顺序遍历这棵树以此来管理和更新所有的Hooks。如果Hooks不在顶层使用,而是被放在条件语句或循环中,就会打乱Hooks调用的顺序,导致React无法正确地将状态、更新函数或副作用与组件的特定渲染关联起来。这可能会导致无限循环、丢失状态或无法更新的副作用等问题

什么是Fiber

FiberNode 是 React Fiber 架构中的核心概念之一,用于表示组件的层级结构和渲染过程中的任务,fiber是一种流程让出机制,它能让react中的同步渲染进行中断,并将渲染的控制权让回浏览器,从而达到不阻塞浏览器渲染的目的

1、数据结构方面

    属性众多:

  • tag:表示 FiberNode 的类型,如 HostComponent、ClassComponent、FunctionComponent 等。
  • key:组件的唯一标识符,用于列表渲染中元素的重用。
  • type:表示组件元素的类型。对于 FunctionComponent,指函数本身;对于 ClassComponent,指 class;对于 HostComponent,指 DOM 节点 tagName。
  • stateNode:指向组件实例,可能是一个 DOM 元素(对于原生组件),也可能是一个类实例(对于自定义组件)。
  • child// 当前节点所关联的子节点
  • sibling// 当前节点所关联的兄弟节点
  • return:指向父级 FiberNode,通过它可以在子 Fiber 节点及其兄弟节点完成工作后返回父级节点。
  • dependencies:表示组件的依赖项。
  • flags 和 subtreeFlags:表示 FiberNode 的状态标志。
  • deletions:表示待删除的节点。
  • lanes 和 childLanes:表示调度的优先级。
  • alternate:指向 FiberNode 的替代节点。在渲染过程中,React 会创建两个 FiberNode,一个表示当前渲染状态,一个表示下一次渲染状态,通过 alternate 属性在两个状态之间进行比较,找出需要更新的节点。

 

 

2、工作原理方面

  • 任务划分:FiberNode 将 React 中的渲染任务拆分到每一帧,使渲染任务变成一个个可以中断和恢复的小任务。
  • 它支持增量渲染,能暂停、终止以及恢复之前的渲染任务(没渲染时间了就将控制权让回浏览器)。
  • 还能为不同任务赋予优先级,实现并发处理,让 React 始终处理最高优先级的任务(让优先级高的运行,比如事件交互响应,页面渲染等,像网络请求之类的往后排)。通过MessageChannel + requestAnimationFrame 模拟实现了requestIdleCallback 让出控制权(window.requestIdleCallback()方法插入一个函数(callback又能接收一个由浏览器告知你执行剩余时间的参数IdleDeadline),这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应).

我们可以通过如下代码来故意造成耗时的场景,然后再来查看剩余时间:

// 用于造成耗时情况的函数
const delay = (time) => {
  let now = Date.now();
  // 这段逻辑会占用time时长,所以执行完它需要time时间
  while (time + now > Date.now()) {};
}

// 待办事项
let work = [
  () => {
    console.log('任务1')
    // 故意占用1S时间
    delay(1000);
  },
  () => {
    console.log('任务2')
    delay(1000);
  },
  () => {
    console.log('任务3')
  },
  () => {
    console.log('任务4')
  },
];

const process = (deadline) => {
  // 通过deadline.timeRemaining可获取剩余时间
  console.log('deadline', deadline.timeRemaining());
  // 还有剩余时间吗?还有剩余工作吗?如果都满足,那就再做一个任务吧
  if (deadline.timeRemaining() > 0 && work.length > 0) {
    work.shift()();
  }
  // 如果还有任务,继续调用requestIdleCallback
  if (work.length) {
    window.requestIdleCallback(process);
  }
}
window.requestIdleCallback(process);

  

 

  • 支持并发处理,灵活调整处理顺序(结合第3点理解,面对可变的一堆任务,react始终处理最高优先级,灵活调整处理顺序,保证重要的任务都会在允许的最快时间内响应,而不是死脑筋按顺序来),避免浏览器渲染被阻塞

3、react中的fiber是如何运转的?

协调阶段:这个阶段做的事情很多,比如fiber的创建diff对比等等都在这个阶段。在对比完成之后即等待下次提交,需要注意的是这个阶段可以被暂停。

提交阶段:将协调阶段计算出来的变更一次性提交,此阶段同步进行且不可中断(优先保证渲染)。

通过fiber的协调阶段,我们了解了diff的对比过程,如果将fiber的结构理解成一棵树,那么这个过程本质上还是深度遍历,其顺序为父---父的第一个孩子---孩子的每一个兄弟。

通过源码,我们了解到reactdiff是同层比较,最先比较key,如果key不相同,那么不用比较剩余节点直接删除,这也强调了key的重要性,其次会比较元素的type以及props。而且这个比较过程其实是拿旧的fiber与新的虚拟dom在比,而不是fiberfiber或者虚拟dom与虚拟dom比较,其实也不难理解,如果keytype都相同,那说明这个fiber只用做简单的替换,而不是完整重新创建,站在性能角度这确实更有优势。

最后,附上fiber更新调度的执行过程:

 

4、应用场景方面

  • React 组件渲染:在 React 应用中,从根组件开始,每个组件都会被转换为一个 FiberNode,构建成 Fiber 树。React 通过遍历 Fiber 树来进行组件的渲染、更新和卸载等操作。例如,当组件的状态或属性发生变化时,React 会根据 FiberNode 记录的信息,高效地计算出需要更新的部分,然后只更新这些部分,而不是重新渲染整个组件树,提高了渲染性能。
posted @ 2025-06-19 10:35  ~小晨晨  阅读(56)  评论(0)    收藏  举报