- Published on
React Interview
- Authors
- Name
- Et cetera
- 对 React 的理解,它有哪些优缺点?
- 什么是 Fiber? Why Fiber
- FiberRoot 和 rootFiber 的区别
- 不同 fiber 之间如何建立起关联的?
- React 调和流程?
- 两大阶段 commit 和 render 都做了哪些事情?
- Fiber 的调和能中断吗? 如何中断?
- Fiber 深度遍历流程?
- 什么是双缓冲树? 有什么作用?
对 React 的理解,它有哪些优缺点?
React 是一个由 Facebook 推出的
UI 框架
,主要用于构建大型、高效、可维护的 Web 应用程序.它的优点包括:组件化
: React 将页面拆分成多个组件,每个组件有自己的状态和生命周期方法,使得代码更加模块化、可维护高性能
: React 使用虚拟 DOM 技术,通过比对前后两个虚拟 DOM 的差异来更新页面,避免频繁操作真实 DOM,从而提高性能生态丰富
: React 生态非常丰富,有大量的插件和工具可以帮助开发者提高效率
它的缺点包括:
- 入门门槛高: React 的学习曲线较陡峭,需要掌握 JSX、组件化思想、生命周期等概念
- 状态管理复杂: 随着应用规模的增大,状态管理可能变得十分复杂,需要使用 Redux 等第三方库来解决
什么是 Fiber? Why Fiber
Why Fiber:
- 因为
JavaScript
引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待.React 15
版本Stack Reconciler
渲染界面时,从开始到渲染完成整个过程是一气呵成的,无法中断 如果组件较大,那么 js 线程会一直执行,然后等到整棵VDOM
树计算完成后,才会交给渲染的线程.这就会导致一些用户交互、动画等任务无法立即得到处理,导致卡顿的情况 Fiber
便是 React 团队对该问题的解决方案的产物,于React 16
正式发布.Fiber
是对核心算法的重写,其中主要做了以下操作:- 为每个增加了优先级,优先级高的任务可以中断低优先级的任务.然后再重新,注意是重新执行优先级低的任务
- 增加了异步任务,调用
requestIdleCallback
api,浏览器空闲的时候执行 dom diff 树
变成了链表,一个 dom 对应两个fiber
(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
- 因为
总结:
- 从架构角度来看,
Fiber
是对 React 核心算法(即调和过程)的重写 - 从编码角度来看,
Fiber
是 React 内部所定义的一种数据结构,它是Fiber 树
结构的节点单位,也就是React 16
新架构下的虚拟 DOM- 一个
fiber
就是一个JavaScript对象
,包含了元素的信息、该元素的更新操作队列、类型 - 每个 Fiber 节点有个对应的 React element,多个 Fiber 节点根据如下三个属性构建一颗树:
// 指向父级Fiber节点 this.return = null // 指向子Fiber节点 this.child = null // 指向右边第一个兄弟Fiber节点 this.sibling = null
- 一个
Fiber
的工作愿景:- pause work and come back to it later(暂停工作,并且能之后回到暂停的地方)
- assign priority to different types of work(安排不同类型工作的优先级)
- reuse previously completed work(之前已经处理完的工作单元,可以得到重用)
- abort work if it's no longer needed(如果后续的工作不再需要做,工作可以直接被终止)
- 从架构角度来看,
FiberRoot 和 rootFiber 的区别
两者关系:
在应用中 ReactDOM.render
可以被多次调用, 它们可以拥有不同的rootFiber(FiberNode)
, 但是整个应用的根节点只能是一个, 那就是fiberRoot(fiberRootNode)
FiberRoot(fiberRootNode)
它是整个应用的根节点, 绑定在container._reactRootContainer
, 也就是绑定在真实 DOM 节点的_reactRootContainer
属性上, 就算是重复调用ReactDOM.render
, FiberRoot(fiberRootNode)
还是不变的, 内部做了判断
rootFiber(fiberNode)
rootFiber
是<App/>
所在组件树的根节点,rootFiber
在每次重新渲染的时候会重新构建. 本身就是一个普通的 fiberNode, 上面通过createHostRootFiber(tag)
创建, 其实最终还是通过createFiber
函数创建的.
在 React Fiber 架构中,Fiber Root 和 Root Fiber 是两个不同的概念.
Fiber Root 是指整个 React 应用的根节点,在 React DOM 应用中通常就是一个 HTML 文档中的某个节点(比如 id 为"root"的 div),在 React Native 应用中则类似.Fiber Root 的主要作用是存储当前的渲染任务,并提供对整个应用内的 Fiber 节点的访问入口.
而 Root Fiber 则是每个组件的顶层 Fiber 节点,它包含了组件的类型、props、state 等信息,并链接到其它子 Fiber 节点.每个组件都有自己独立的 Root Fiber,它们之间通过父子关系相互连接起来,形成了整个应用的 Fiber 树.
因此可以看出,Fiber Root 是整个应用的根节点,而 Root Fiber 则是每个组件的最上层 Fiber 节点,它们分别扮演着不同的角色,在 React Fiber 架构中都具有非常重要的作用.
不同 fiber 之间如何建立起关联的?
在 React Fiber 架构中,不同的 Fiber 节点之间可以通过 parent
、sibling
和 child
三个指针建立起关联.
具体来说,每个 Fiber 节点都包含着对应组件的类型、props、状态等信息,同时还会有一个指向其父节点的 parent 指针、一个指向它的下一个兄弟节点的 sibling 指针、以及一个指向它的第一个子节点的 child 指针.这样一来,每个 Fiber 节点都可以通过这三个指针访问到自己的父节点、兄弟节点和子节点,从而形成了整个应用的 Fiber 树结构.
举个例子,当我们在组件中访问 this.props
或者调用 setState
时,React 会根据当前组件的 Fiber 节点来进行相应的处理,然后通过 Fiber 树结构找到对应的父节点,并更新其 child 或 sibling 指针,从而实现对整个应用状态的更新和渲染.
总之,在 React Fiber 架构中,不同 Fiber 节点之间是通过 parent、sibling 和 child 三个指针进行关联的.这种 Fiber 树结构的建立使得 React 能够高效地进行组件更新和渲染,从而提高了 React 应用的性能.
React 调和流程?
React 的调和(Reconciliation)是指 React 在组件更新时,如何通过一种高效的算法来判断哪些组件需要重新渲染,并对这些组件进行更新.React 调和的过程可以分为两个阶段,即 render 阶段和 commit 阶段.
在 render
阶段,React 会从根节点开始遍历整个组件树,检测每个组件是否需要更新.具体来说,React 会根据每个组件的 props 和 state 来计算出该组件在当前状态下的输出结果,并将此结果与该组件上一次输出的结果进行比较,以判断该组件是否需要更新.如果该组件需要更新,则 React 会调用组件的生命周期函数,从而产生新的输出结果.
在 commit
阶段,React 会根据 render 阶段的计算结果,对需要更新的组件进行实际的更新操作,包括创建、插入、更新、删除等.同时,由于 React 使用了虚拟 DOM 来描述组件树,因此实际的更新操作并不是立即执行的,而是先在虚拟 DOM 中进行,然后再统一批量更新到真实的 DOM 中去,从而提高了更新效率.
总之,React 调和的流程包括 render 阶段和 commit 阶段,其中 render 阶段用于判断组件是否需要更新,而 commit 阶段则用于实际的更新操作.通过这种流程,React 能够高效地进行组件更新和渲染,从而提高了应用的性能和用户体验
两大阶段 commit 和 render 都做了哪些事情?
在 React 中,调和(Reconciliation)过程分为两个阶段:render 阶段和 commit 阶段.这两个阶段分别做了以下事情:
Render
阶段读取组件的 Props 和 State 数据.
根据读取到的数据,计算出组件的下一个状态(Next State).
将 Next State 和现有的 Props 数据传递给组件函数.
组件函数根据 Next State 和 Props 返回一个新的 Virtual DOM(虚拟 DOM).
React 比较新旧两个 Virtual DOM,找出需要更新的部分(Diff 算法).
标记需要更新的组件,以及其子孙组件.
Commit
阶段为需要更新的组件创建新的 DOM 元素.
将新的 DOM 元素插入到页面中,或者替换旧的 DOM 元素.
更新组件的 Props 和 State,从而让组件重新执行 Render 阶段,并计算出新的 Virtual DOM.
递归地进行上述操作,直到所有标记为更新的组件都被处理完毕.
总之,在 Render
阶段,React 通过比较新旧两个 Virtual DOM 找出需要更新的部分,并标记需要更新的组件;而在 Commit
阶段,React 则对标记为更新的组件进行实际的 DOM 操作,并递归地更新其子孙组件,从而完成整个组件树的更新和渲染.通过这样的高效算法和优化策略,React 可以提高组件更新和渲染的性能,同时保证组件的正确性和可靠性.
Fiber 的调和能中断吗? 如何中断?
Fiber 是一种用户模式线程,不同于操作系统的内核模式线程,因此不能像内核模式线程那样被操作系统的调度器直接中断.在 Fiber 中,需要程序员自己进行协作式调度,即在 Fiber 完成任务或者需要等待某些事件时主动将 CPU 使用权交出去,这样其他 Fiber 就可以有机会执行.因此,Fiber 的调度方式基于协作而非抢占
在 React Fiber 中,调度器可以使用requestIdleCallback
函数来请求浏览器在空闲时间执行任务.该函数的参数是一个回调函数,具体的调度逻辑可以在回调函数中实现.如果在任务执行过程中需要中断,只需要及时 return 即可.Fiber 还提供了unstable_scheduleCallback
函数支持设置任务的优先级以及截止时间,程序员可以在这个函数中根据任务的优先级和截止时间来决定是否中断当前任务调度.同时,React Fiber 还提供了一种优先级抢占的机制,即在任务的开始、结束等关键时间点检测是否有更高优先级的任务需要执行,如果有,则立即中断当前任务的调度,开始执行更高优先级的任务
Fiber 深度遍历流程?
Fiber 是 React 中用于实现协调更新的一种机制,它采用了深度遍历算法来实现对组件树的更新.下面是 Fiber 深度遍历算法
的流程:
- 首先从根节点开始遍历,将根节点包装为 Fiber 节点
- 查看当前节点是否有子节点,如果有,将第一个子节点包装为 Fiber 节点,作为当前节点的 child 属性,并将当前节点作为子节点的 return 属性.
- 如果当前节点没有子节点,检查当前节点的兄弟节点,如果有,将兄弟节点包装为 Fiber 节点,作为当前节点的 sibling 属性,并将当前节点的父节点作为兄弟节点的 return 属性.
- 如果当前节点即没有子节点,也没有兄弟节点,那么就回溯到当前节点的父节点,进入到父节点的步骤 2.
在整个遍历过程中,Fiber 节点对象除了包含组件的状态和属性外,还会记录本次更新所需要的信息,如 setState 的参数值、是否需要更新等,以便在后续的更新处理中快速定位需要更新的内容,优化渲染性能.同时,由于 Fiber 采用了可中断的算法,可以在遍历的任意一个节点处中断操作,从而让出 CPU 时间片,优化用户交互响应速度.
需要注意的是,在 Fiber 深度遍历的过程中,每一个节点都会构建一份 fiber 树,这个树叫做 workInProgress tree,也就是在进行中的任务树.当任务结束后,将 workInProgress tree 替代 current Fiber,变成新的 current Fiber,用于下一次任务的计算
什么是双缓冲树? 有什么作用?
双缓冲树是
React 中的一个概念,指的是使用两个虚拟 DOM 树.其中一个树用于当前页面的展示,另一个树用于计算需要进行更新的部分.React 会将计算好的更新部分与当前页面的展示进行比较,只对有变化的部分进行更新,从而减少 DOM 操作的次数,提高了页面的渲染性能
双缓冲树在 React 的渲染过程中发挥了重要作用.由于 render
阶段构建 workInProgress 树
的过程是可以中断的,同时,workInProgress 树
最终又会在 commit
阶段渲染到浏览器页面上,这就决定了在 render 阶段,必须要保持浏览器页面不变直到 render 阶段完成.因此,双缓冲树的概念就解决了这个问题,它保证了在 render 过程中,仅在虚拟 DOM 树中进行操作,直到整个渲染完成后,才将更新后的虚拟 DOM 树渲染到浏览器页面上,从而保证了页面的渲染效率和稳定性