Skip to content

React 面试 ​

什么是 fiber? ​

fiber 是一个虚拟 dom,是一个 链表结构,返回了 return, children, siblings,分别代表 父 fiber,子 fiber 和兄弟 fiber。 每一个 fiber 是一个任务单元,可以中断/挂起/恢复。

React 常用的 fiber 类型, fiber 是个对象, 有很多种类, 比如函数组件, 类组件, dom 元素, 文本节点。 每一个 fiber 都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间 expirationTime( v17 版本叫做优先级 lane )来判断是否还有空间时间执行更新,如果没有时间更新,就要把主动权交给浏览器去渲染,做一些动画,重排( reflow ),重绘 repaints 之类的事情。

什么是时间切片? ​

每一帧花 5ms 来更新,调和每一个 fiber 节点的时候,记录开始时间,如果当前时间超过 5ms,则开启一个宏任务(MessageChannel),等待浏览器下一帧继续调和,如果当前有紧急任务插入,比如 click 事件,则优先执行紧急任务。

什么是并发渲染? ​

优先执行紧急更新,然后再执行不紧急的更新。 对于每次更新分配一个 lane,区分它的紧急程度。通过 fiber 结构将不紧急的更新拆分成多段,通过宏任务拆分到浏览器中不同帧中。

React Router 基本原理 ​

1.基于 hash 实现

通过监听 hashchange 方法

2.基于 history 实现

重写 history.pushState, history.replaceState 从而可以监听到事件。 对于浏览器的前进和后退,监听 popstate 方法。

什么是虚拟 DOM? ​

虚拟 DOM 本质是一个 js 对象,通过虚拟 DOM 就能够完整的绘制出对应真实的 DOM。 虚拟 DOM 本质上就是用一种数据结构来描述界面节点, 借助虚拟 DOM, 带来了跨平台的能力, 一套代码多端运行

1. 优点 ​

简单方便: 如果频繁手动操作复杂页面的真实 dom, 繁琐容易出错。 性能方面: 能够避免频繁操作真实 dom,减少回流和重绘。 跨平台: 由于 虚拟 DOM 本质上就是用一种数据结构来描述界面节点,所以有跨平台能力。

2. 缺点 ​

  • 首次渲染的时候把虚拟 dom 转换成真实 dom,比直接操作真实 dom 慢。
  • 虚拟 dom 内存中维护一个额外的虚拟状态,会导致一定的内存消耗。

diff 算法 ​

非 list 结构是逐层对比。 list 结构,根据 key 和 tag 判断是否为相同节点。

  1. 如果新节点比旧节点少,新节点遍历完之后,把旧节点的多余的删除。 oldChild: ABCD newChild: AB

  2. 如果是新节点比旧点多,新节点遍历完之后,发现没有 olderFier,直接创建新节点。 oldChild: AB newChild: ABCD

  3. 如果节点位置发生了改变,则在旧节点创建一个 map,存储老的 fiber 和对应的 key 的映射,然后去复用老的 fiber oldChild: ABCD newChild: ABDC

  4. 复杂情况(删除 + 新增 + 移动) 首先 A 被复用,发现 E 的时候,首先会 oldChild 创建一个 map,发现没有 E,则新创建 E,D 和 B 然后复用,最后 C 删除。 oldChild: A B C D newChild: A E D B

React 没有采用双端 diff,是因为 react 的链表是单向(slibing 指向下一个节点),而不是双向。即使使用双端算法,也应该是在改变比较少的情况下,而不是直接替代 Map。

hook 对比 class 的优势 ​

  1. 逻辑复用
  2. 灵活: hook 可以嵌套 hook
  3. 组件拆分更容易。

缺点:

  1. Hooks 的有一定的规则,不能在条件判断里面写。
  2. 不正确的写容易造成死循环,和性能问题。

另外 ErrorBoundary 只能使用 class 组件来写,因为 hooks 没有实现 getDerivedStateFromError, componentDidCatch 生命周期。所以不能用来捕获错误。

useInsertionEffect, useLayoutEffect, useEffect ​

useInsertionEffect 是 Dom 变更之前,主要给 css-in-js useLayoutEffect Dom 已经变更了,在内存里,但是还没渲染出来 useEffect Dom 已经渲染到页面上了,是一个异步的方法

高阶函数 ​

参数为组件, 返回值为新组件的函数。

事件原理(合成事件) ​

React 事件绑定到根元素上。并且 onChange 事件是由 blur,change,focus 多个事件合成。 当用户点击一个元素的时候,会收集这个元素上的绑定的事件,并且收集这个元素父级绑定的事件,然后形成事件队列,对于捕获的事件放在数组前面,对于其它事件 push 到数组里,然后依次执行里面的事件,如果这个时候某个事件里面阻止冒泡了,则其它事件都不执行了。

React 17 和 18 的区别 ​

1. 自动 Batching ​

React 把多次的状态更新(state updates),合并到一次渲染中。 比如在 onClick 事件里面,如果 setState 写在异步操作里面,则 2 次 setState 会触发 2 次 re-render。

2. 从同步不可中断更新变成了异步可中断更新 ​

useTransition 执行返回一个数组。数组有两个状态值:

  • isPending: 指处于过渡状态,正在加载中
  • startTransition: 通过回调函数将状态更新包装起来告诉 React 这是一个过渡任务,是一个低优先级的更新,允许标记更新作为一个过渡阶段,React 可以被中断执行。
js
const [isPending, startTransition] = useTransition();

useSyncExternalStore 它通过强制的同步状态更新,使得外部 store 可以支持并发读取。主要给第三方的库 redux 使用。

useInsertionEffect 允许 css-in-js 库解决在渲染中注入样式的性能问题。执行时机在 useLayoutEffect 之前。

useDeferredValue产生一个新的状态是延时状态。

3. React17 的 SSR 架构 一切都是串行的,React18 提供 Suspense,优化前端的加载速度和可交互的等待时间 ​