记!为什么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
更新调度的执行过程: