面试-React底层运行简记

1,090 阅读6分钟

原文链接

将之前研究过的React面试问题分享出来,希望可以帮助到大家。

1. JSX背后的功能模块是什么,这个功能模块做了哪些事情?

JSX通过babel语法转换之后,实际就是通过React.createElement函数来创建React元素。createElement接收三个参数type类型,config配置,children子元素。通过createElement就可创建出虚拟DOM对象。

2. React团队为什么要去掉旧的生命周期函数?

React16+引入Fiber架构,Fiber会将一个大的更新任务拆解为多个小任务,而且它是可中止,可恢复的。React16+生命周期被划分为rendercommit两个阶段。render阶段在执行过程中允许被打断,而commit阶段操作涉及真实DOM渲染,是不可打断的。

ComponentWillMount
ComponentWillUpdate
ComponentWillReceiveProps
这些生命周期,它们都处于render阶段,而在Fiber架构中,render阶段会被打断,重复被执行。在这些生命周期可能习惯做的事情可能有:setState、异步请求、操作真实DOM等。而在Fiber异步渲染控制下,这些生命周期可能会导致非常严重的bug(例如在这些废弃的生命周期中调用支付接口)。

3. 为什么是React Hooks?

相对于Class组件,函数组件更加轻量,更加符合UI=render(data)特点。同时在Fiber架构的加持下,Hooks的实现不是问题。配合函数组件的发展,Hooks应运而生,从而是函数组件真正把数据和渲染绑定到一起。当然Hooks也还是存在部分不足:部分周期不存在;不能很好的消化“复杂”,组件的拆分和组织是一个大的挑战,不易把握。

4. 为什么Hooks执行顺序如此重要?

Hooks本质是链表。例如 使用useText、useState创建state时,hook创建的state会以单链表形式保存,更新时,函数组件重新调用,hooks会依次遍历单链表,读取数据并更新,这一过程完全按照创建时的顺序来的。因此当更新时,位置一旦改变,执行顺序被替换,运行就会出现bug。

5. 调和(协调)和diff的关系或区别?

调和指的是虚拟DOM映射到真实DOM的过程。调和过程并不能喝diff画等号。调和是“使一致”的过程,而diff是“找不同”的过程,它只是“使一致”过程中的一个环节。(当然常说的调和相关问题多半就是diff过程的)

6. react的diff逻辑和思路?

1.因为时间付扎渡的原因,diff过程只针对同层的节点作比较;
2.对于同类型的组件,才有进一步对比的必要性;
3.对于列表组件,通过key属性来维持节点的稳定性,避免总是生产新节点;

7. setState的工作流是怎么样的?

非并发(**concurrent**)模式setState会出现异步和同步的现象。在生命周期和合成事件中是同步,而在setTimeout、setInterval、DOM原生函数等函数中是同步的。那么这是为什么尼?在合成事件或生命周期执行时,批量更新的任务锁就被开启了,我们所做的setState操作会被放入到批量更新队列中,直到函数执行完,批量更新的任务锁才会被关闭。批量更新的任务锁是一个同步操作,而一旦你在setTimeout函数使用setState,此时setTimeout函数回调会被放入下一个宏任务执行,而当setState执行时,批量更新的任务锁时关闭的,它就不会放入到批量更新队列中,而是直接执行。

并发(**concurrent**)模式setState不会出现异步和同步的现象。因为存在时间切片,只要当前时间片没有结束,依旧可以将多个 setState 合并成一个,即使是在setTimeout中被调用。而对于超过当前时间片的操作,会通过MessageChannel放入到下一个宏任务中继续执行。(MessageChannel接收消息的时机比 Promise 所在的 microTask 要晚,但是早于 setTimeout)

8. Stack Reconciler栈调和 有怎么样的局限性?

浏览器中Js线程和渲染线程是互斥的。这两个线程不能穿插执行,必须串行。而当Js线程长时间占用主线程,那么渲染线程的更新就不得不长时间的等待,这时就会导致页面卡顿。

Stack Reconciler栈调和是一个同步递归过程,虚拟DOM树diff算法遍历是深度优先遍历。由于它是同步的,不可在被打断。当处理结构复杂,体量庞大的虚拟DOM树时,Stack Reconciler时间会很长,以为这Js主线程长时间占用主线程,进而导致上述中说道的渲染卡顿/页面卡死。

9. 说一说Fiber架构?

特点:可中断、可恢复、存在优先级。

 Scheduler ————> Reconciler ————> Renderer
 更新优先级         找不同         渲染不同

Fiber架构模式下,每个更新任务会被赋予一个优先级。当然有任务A进入调度器,这个任务优先级更高,而Reconciler中已有任务B在执行,那么,Reconciler会将任务B终止,更高优先级的任务A被推入Reconciler。当A任务完成之后,新一轮调度会将之前中断的任务B重新推入Reconciler,继续它的渲染之旅。

render开始 ——————> (工作单元| 工作单元 | 工作单元) ——————> commit提交渲染

10. ReactDOM.render调用栈的初始化阶段、render阶段

初始化阶段:会创建root对象这个对象挂载_internalRoot属性,而_internalRoot也就是FiberRootFiberRoot的本质是一个FiberRootNode对象,其中包含current属性,current对象是一个FiberNode实例。current对象就是一个Fiber节点,并是Fiber树的头部节点;确定Fiber的优先级,结合优先级创建当前Fiber的update对象,并将其入队调度FiberRoot;接下来进入render阶段;(此时相当于只有一个Fiber头部节点)

render阶段:通过createWorkInProgress函数,创建rootFiber节点的副本workInProgress节点树(即current节点的副本节点),他们通过alternate互相引用;接着会触发beginWork函数,进而实现对新的Fiber节点的创建。循环遍历,组件元素Fiber会不断被创建(每个元素节点对应一个Fiber节点),直到创建到最后一个为止,此时Fiber树(单链表)基本完成;重点,此时已经遍历到了单链表的最底部节点,然后会由下自上的依次生成真实DOM节点,同时被它的父组件副作用链,这个副作用链也是一个单链表,直遍历到根节点,此时的根节点上的副作用链就包含的全部的DOM更新。那么剩下的只需要拿到root下的副作用链更新即可了。

以上内容完结!希望以上内容可以帮助到你。

本文正在参加「金石计划」