Skip to content

React相关面经整理

Posted on:2024年1月20日 at 22:33

架构相关

Fiber

产生原因

传统的虚拟dom在递归更新时无法中断,如果组件树层级较深时会占用大部分线程时间,使得页面卡顿。

Fiber的引入改变了这一情况。Fiber可以理解为是React自定义的一个带有链接关系的DOM树,每个Fiber都代表了一个工作单元,React可以在处理任何Fiber之前判断是否有足够的时间完成该工作,并在必要时中断和恢复工作。

其实也可以理解为增强的虚拟dom。

是什么?

React内部实现的一套状态更新机制。支持任务不同 优先级,可中断与恢复,并且恢复后可以复用之前的 中间状态

fiber怎么找到断点继续开始

// packages/react-reconciler/src/ReactFiberWorkLoop.js

JSX到页面dom的过程

JSX 经过 babel 转换后会变成 React.createElement(...) 形式的 js 代码,返回的是一个 React Element实例,也就是虚拟dom。

JSX 是一种描述当前组件内容的数据结构,他不包含组件 schedulereconcilerender所需的相关信息。

在组件 mount 时,Reconciler根据 JSX描述的组件内容生成组件对应的 Fiber节点

在组件 update 时,ReconcilerJSXFiber节点保存的数据对比,生成组件对应的 Fiber节点,并根据对比结果为 Fiber节点打上 标记

最后进入 Render 阶段,将fiber转化为真实dom并显示在页面上。

React diff

Diff的执行位于render阶段的 update

对于 update的组件,他会将当前组件与该组件在上次更新时对应的Fiber节点比较(也就是俗称的Diff算法),将比较的结果生成新Fiber节点。


一个 DOM节点在某一时刻最多会有4个节点和他相关。

  1. current Fiber。如果该 DOM节点已在页面中,current Fiber代表该 DOM节点对应的 Fiber节点
  2. workInProgress Fiber。如果该 DOM节点将在本次更新中渲染到页面中,workInProgress Fiber代表该 DOM节点对应的 Fiber节点
  3. DOM节点本身。
  4. JSX对象。即 ClassComponentrender方法的返回结果,或 FunctionComponent的调用结果。JSX对象中包含描述 DOM节点的信息。

Diff算法的本质是对比1和4,生成2。

SSR

SSR的整个过程,Next.js做了什么

为什么SSR的首屏时间会更短

之所以说ssr快,主要体现在http请求数量上,ssr只需要请求一个html文件就能展现出页面,虽然在服务器上会调取接口,但服务器之间的通信要远比客户端快,甚至是同一台服务器上的本地接口调取。

spa慢主要慢在需要请求大量的js资源,一般的首页,请求10几个js都算少到,要知道js的加载是同步的,页面逻辑可能藏在最后几个js里面,请求-等待-下载-然后是解析js-再调接口-展现页面。

React各版本区别

v16

重写底层逻辑,引入fiber架构

引入Hooks (16.7)

v17

全新的jsx转换器

副作用清理时机

useEffect 副作用清理函数由同步变为异步执行,如果要卸载组件,则清理会在屏幕更新后运行

启发式更新算法更新

React17新特性:启发式更新算法

v18

Concurrent Mode 并发模式

Concurrent Mode(并发模式)

setState 自动批处理

React 18 之前,我们只在 React 事件处理函数 中进行批处理更新。默认情况下,在 promisesetTimeout原生事件处理函数中、或 任何其它事件内的更新都不会进行批处理。

React 18 之后,任何情况都会自动执行批处理,多次更新始终合并为一次。

更新 render API

v18 使用 ReactDOM.createRoot()代替 ReactDOM.render(),创建一个新的根元素进行渲染,使用该 API,会自动启用并发模式。

Suspense 支持 SSR

可以使用 <Suspense> 来将程序分解成较小的独立单元,允许服务端分组件返回页面,有选择的让重要的部分首先加载与水合。

useTransition

Hooks

useState

执行过程

什么时候需要传入一个函数作为参数?

通常用于优化性能,也称为惰性初始化。

const initialState = Number(window.localStorage.getItem("count"));
const [count, setCount] = React.useState(initialState);

每次组件更新时都会重新执行一遍第一行的代码,当localstorage数据偏大时有不小的性能开销。

const initialState = () => Number(window.localStorage.getItem("count"));
const [count, setCount] = React.useState(initialState);

initialState 以函数形式传入时,它只会在函数组件初始化的时候执行一次,规避不必要的性能问题。

异步更新与批处理更新时需要注意

在setState时需要传入一个函数,获取之前的state值保证每次获取到的是最新

let [num, setNum] = useState(0)
const add = () => {
  let = num = 0
  setNum(num + 1);
  setNum(num + 1);
  setNum(num + 1);
  console.log(num); // 1
}

传入函数作为参数,确保每次访问的值是更新后的值。

let [num, setNum] = useState(0);
const add = () => {
  setNum(num => num + 1);
  setNum(num => num + 1);
  setNum(num => num + 1);
  console.log(num); // 3
};

异步操作state值时情况类似,也需要传入函数操作。

useCallback

useMemo

性能优化

组件

受控/非受控

通信方式

Redux

如何理解redux的三大原则,为什么必须这样?

  1. 单一数据源

    使得状态之间更好管理,更容易调试,以及一些撤销/重做的功能更容易。同时解决了Flux中存在的多的Store带来的Store互相依赖问题。

  2. State 是只读的

    更方便定位问题、追踪数据,避免竞态问题的出现。

  3. 使用纯函数来修改state

    借鉴了函数式编程,纯函数的好处是没有副作用,相同的输入,永远都会有相同的输出。这样做的好处是这个函数可以不依赖外部环境,更加解耦,更容易调试以及更容易的写单元测试。

useContext与useReducer

  1. userReducer获取 statedispatch
  2. userContextstatedispatch共享到子组件

与Redux的区别

仍然强耦合于UI,生命周期局限在函数组件内部,并没有做到分离。一般用于只有少部分组件需要深层传递数据时的简易方案使用。