记!为什么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、数据结构方面
属性众多:

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的结构理解成一棵树,那么这个过程本质上还是深度遍历,其顺序为父---父的第一个孩子---孩子的每一个兄弟。
通过源码,我们了解到react的diff是同层比较,最先比较key,如果key不相同,那么不用比较剩余节点直接删除,这也强调了key的重要性,其次会比较元素的type以及props。而且这个比较过程其实是拿旧的fiber与新的虚拟dom在比,而不是fiber与fiber或者虚拟dom与虚拟dom比较,其实也不难理解,如果key与type都相同,那说明这个fiber只用做简单的替换,而不是完整重新创建,站在性能角度这确实更有优势。
最后,附上fiber更新调度的执行过程:


浙公网安备 33010602011771号