什么是 React?

React 是一个简单的 javascript UI 库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。它使用虚拟 DOM 来有效地操作 DOM。它遵循从高阶组件到低阶组件的单向数据流。

React 生命周期

React 组件的生命周期可以分为三个阶段:

  1. 挂载阶段(Mounting)

当 React 首次渲染元件,并把渲染后的节点加入 DOM 中时,我们称这时为 mounting。当 React 把节点加入到 DOM 后,浏览器会渲染并绘制画面。在这个阶段,生命周期将会依照下列的顺序呼叫这些方法:

  • constructor(props)
  • getDerivedStateFromProps(props, state)
  • render()
  • componentDidMount()
  1. 更新阶段(Updating)

在 mounting 后,如果一个元件的 prop 或 state 有变化时,就会触发重新渲染。重新渲染后,React 会去比较虚拟 DOM 有哪些地方改变了,然后将改变的部分更新到实际的 DOM 上。这个阶段我们称它为 updating。当一个 component 被重新渲染时,其生命周期将会依照下列的顺序呼叫这些方法:

  • getDerivedStateFromProps(props, state)
  • shouldComponentUpdate(nextProps, nextState)
  • render()
  • getSnapshotBeforeUpdate(prevProps, prevState)
  • componentDidUpdate(prevProps, prevState, snapshot)
  1. 卸载阶段(Unmounting)

当一个元件被从 DOM 中移除时,我们称之为 unmounting。这时将会呼叫:

  • componentWillUnmount()

React 中发起网络请求应该在哪个生命周期中进行?为什么?

componentDidMount 中发起网络请求。因为 componentDidMount 是在组件挂载到 DOM 后执行的,所以在这个生命周期中发起网络请求可以确保在组件挂载后执行。

state 和 props 触发更新的生命周期分别?

stateprops 触发更新的生命周期相同。但是 getDerivedStateFromPropsshouldComponentUpdategetSnapshotBeforeUpdate 这几个周期中 如果是更新 state,参数 prevState 会有值,nextProps 是一个空对象; 如果是更新 props,参数 nextProps 会有值,prevState 是一个空对象。

虚拟 DOM 的原理是什么?

虚拟 DOM(Virtual DOM,简称 VDOM)是一种 轻量级的 JavaScript 对象,用于模拟真实 DOM 结构。它是 React、Vue 等前端框架中用于 优化页面渲染性能 的关键技术。

当页面状态(state)发生变化时,前端框架不会直接修改真实 DOM,而是先在虚拟 DOM 中进行计算,然后通过 Diff 算法 找到最小变更,并批量更新真实 DOM,提高性能。

更新过程一般分为 3 个阶段:

  1. 创建虚拟 DOM(VNode)

组件初次渲染时,会生成对应的虚拟 DOM 树(VDOM)。
这棵 VDOM 仅仅是一个 JavaScript 对象,不会影响真实 DOM。

  1. Diff 计算(对比新旧 VDOM)

当组件的 state 或 props 发生变化,框架会重新生成新的虚拟 DOM。
通过 Diff 算法,比较新旧虚拟 DOM,找出变化的部分。

  1. 更新真实 DOM(Reconciliation 过程)

根据 Diff 计算结果,框架 最小化真实 DOM 的变更,只更新必要部分。
使用 批量更新(如 React 的 Fiber 机制)提高性能。

虚拟 DOM 的优缺点:

优点:

  • 提升性能:通过 Diff 算法 计算最小更新范围,减少不必要的 DOM 操作,避免频繁重绘和重排(Reflow & Repaint)。React 采用 批量更新Fiber 架构,进一步优化渲染。
  • 跨平台渲染:由于 VDOM 只是一个 JavaScript 对象,它不仅能映射到浏览器 DOM,还能用于 React Native(移动端)、Server-Side Rendering(SSR)等。
  • 代码简洁、易维护:通过 声明式 UI(Declarative UI),开发者只需关注 数据状态,而不必手动操作 DOM,提高开发效率。

缺点:

  • 初次渲染比原生 DOM 慢:由于需要 创建虚拟 DOM计算 Diff,对于静态页面(如普通 HTML)可能反而带来额外开销。
  • Diff 计算的性能开销:Diff 算法虽然高效,但仍然需要一定的 CPU 计算,如果状态频繁变更,可能影响性能(如复杂的动画、大量节点更新)。

什么是 JSX?

JSX(JavaScript XML) 是一种 JavaScript 语法扩展,用于在 React 中编写类似 HTML 的代码。它允许开发者在 JavaScript 代码中直接编写 UI 结构,提高可读性和开发效率。

JSX 不会直接被浏览器执行,它需要通过 Babel 转换为标准 JavaScript,本质上是 React.createElement() 的语法糖。

React 组件的通信方式

  1. 父组件向子组件通信

父组件通过 props 向子组件传递数据。

  1. 子组件向父组件通信

子组件通过 回调函数 向父组件传递数据。

  1. 跨级组件通信
  • props 传递:可能会出现多个层级,需要逐层传递,增加了复杂度。很可能这些 props 并不是中间组件需要的,只是为了传递给子组件。
  • context 传递:context 相当是一个大容器,可以把要通信的内容放在这个容器中,不管嵌套多深,都可使用。对于跨域多层的全局数据可以使用 context 实现。
  1. 非嵌套组件通信
  • 发布订阅:创建一个全局的 event bus(事件总线),然后通过 onemit 来传递消息。
  • redux 等全局状态管理工具:通过 ReduxZustand 等全局状态管理工具来传递消息。

为什么 React 渲染列表时需要加上 key?

在 React 中,渲染列表时需要为每个元素提供唯一的 key,其主要作用是 优化性能、减少不必要的 DOM 操作,并确保 React 正确识别组件的变化

React 使用 Diff 算法 来比较 新旧虚拟 DOM(VDOM),找出需要更新的部分。如果没有 key,React 只能按元素索引 进行 Diff 计算。当列表项顺序发生变化(如插入、删除、排序),React 可能会 错误更新或不必要地重新渲染

props 和 state 的区别

props 是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的 props 来重新渲染子组件,否则子组件的 props 以及展现形式不会改变。

state 的主要作用是用于组件保存、控制以及修改自己的状态,它只能在 constructor 中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的 this.setState 来修改,修改 state 属性会导致组件的重新渲染。

区别:

  • props 是外部传入的,state 是组件内部维护的。
  • props 具有可读性和不变性,state 具有可变性和可读性。
  • props 主要用于组件之间传递数据,state 主要用于组件内部状态管理。

React 单向数据流

单向数据流是指数据的流向只能由父组件通过 props 将数据传递给子组件,不能由子组件向父组件传递数据,要想实现数据的双向绑定,只能由子组件接收父组件 props 传过来的方法去改变父组件的数据,而不是直接将子组件的数据传递给父组件。

如何理解 setState

setState 是 React 中用于更新组件状态的函数。当 setState 被调用时,React 会根据新的状态触发重新渲染,以反映状态变化。

  1. 异步更新

setState 是异步的,它不会立即更新状态,而是将更新状态的操作放入队列中,等当前事件处理完成后再执行。这是为了优化性能,避免频繁的更新状态。

  1. 合并更新

setState 会将传入的部分状态与当前状态合并。这意味着它不会完全覆盖当前状态,而是仅更新改变的部分。

  1. 批量更新

React 会批量更新状态,尤其在事件处理和生命周期方法中,React 会将多个 setState 调用合并成一个更新,以提高性能。

1
2
3
4
5
handleClick = () => {
this.setState({ counter: this.state.counter + 1 });
this.setState({ name: 'Alice' });
// React 会将这两个 setState 合并成一次更新
}

React Fiber

React Fiber 是 React 16 引入的一种新的调度算法,用于优化 React 的渲染过程。它将渲染过程拆分为多个小任务,并使用时间片来分配任务,从而提高渲染效率。

核心概念

  • 可中断渲染:在 Fiber 之前,React 的渲染过程一旦开始就无法中断,这被称为”同步渲染”。当组件树很大时,这会导致主线程被长时间占用,造成用户交互、动画等卡顿。Fiber 引入了可中断的渲染过程,允许 React 根据优先级暂停和恢复渲染工作。
  • 优先级调度:Fiber 允许根据任务的重要性分配不同的优先级。例如,用户交互响应的优先级会高于数据更新的渲染。
  • 增量渲染:将渲染工作分解成多个小单元,而不是一次完成整个组件树的渲染。
  • 时间片:React 将渲染工作分解成小单元,每个单元执行完后,会检查是否有剩余时间继续工作,如果没有,就会让出控制权给浏览器。

React Fiber 的实现原理

  • Fiber 节点结构:Fiber 是一种链表结构,每个 Fiber 节点对应一个 React 元素,包含了组件的类型、状态、副作用等信息。
  • 双缓冲技术:React 使用”双缓冲”技术,维护两棵树,一颗是 current 树,另一颗是 workInProgress 树。current 树是当前屏幕上显示的内容对应的 Fiber 树,workInProgress 树是正在构建的新 Fiber 树。这种机制允许 React 在后台构建新的树,而不影响当前显示的内容。当 workInProgress 树构建完成后,React 通过简单地切换指针使其成为新的 current 树。

React Hooks解决了什么问题?

1. 组件逻辑复用困难

在 Hooks 之前,组件逻辑复用通常通过高阶组件(HOC)或渲染属性(Render Props)来实现。然而,这些方法会导致组件嵌套过深,难以维护。

2. 复杂组件难以理解

类组件中,相关逻辑可能分散在不同的生命周期方法中,导致代码难以阅读和理解。

3. this 的困惑

类组件中,this 的指向容易让人困惑。还需要手动绑定事件处理函数。

总的来说,类组件在多年的应用实践中,发现了很多无法避免问题而又难以解决。而相对类组件,函数组件又太过于简陋,比如没有状态,没有生命周期,所以 React Hooks 就是为了解决这些问题而出现的。它使得函数组件可以拥有状态,可以拥有生命周期,可以拥有组件的上下文,可以拥有组件的 ref 等等,这样就不必将函数组件转换为类组件。

Hooks 的使用限制

  • 只能在函数组件中使用
  • 不能在循环、条件、嵌套函数中使用

原因

Hooks 依赖于调用顺序来正确工作。React 内部维护了一个”链表”来跟踪每个组件中的所有 Hooks。每次组件渲染时,React 期望 Hooks 以完全相同的顺序被调用。如果将 Hooks 放在条件语句、循环或嵌套函数中,React 将可能无法正确地匹配 Hooks 的顺序,从而导致错误。

SSR 和 CSR 的区别

服务端渲染 (SSR, Server-Side Rendering) 是一种页面渲染的技术,它将页面的 HTML 内容在服务器端生成,然后将生成好的 HTML 页面发送到浏览器。这与 客户端渲染 (CSR, Client-Side Rendering) 不同,在客户端渲染中,浏览器加载一个空的 HTML 框架,并通过 JavaScript 动态生成和填充页面内容。

服务端渲染 (SSR) 的工作流程:

  1. 用户请求页面:

用户向服务器发出请求(例如输入网址访问网页)。

  1. 服务器生成 HTML 内容:

服务器接收到请求后,使用服务器端的模板引擎(如Next.js)根据请求的信息动态生成完整的 HTML 页面。这个过程会结合服务器上的数据和模板,生成包含动态内容的 HTML 页面。内容在服务器端渲染好后,直接以完整的 HTML 响应发送到浏览器。

  1. 浏览器接收 HTML 页面:

浏览器接收到完整的 HTML 页面,立即呈现页面内容给用户。由于 HTML 已经包含了所有的内容,用户可以立即看到页面的初步内容。

  1. 后续的客户端渲染:

服务端渲染的页面可能还包含 JavaScript 文件,这些代码会在浏览器中运行,进行客户端的交互和后续的页面更新。

SSR 优点

  1. 更快的首屏加载速度

因为服务端渲染直接将完整的 HTML 页面发送到浏览器,用户在页面加载的初期可以看到完整的内容。相比客户端渲染,SSR 不需要等到 JavaScript 加载并执行后才显示页面内容。

  1. SEO 友好

对于搜索引擎来说,SSR 比 CSR 更友好。因为搜索引擎的爬虫可以直接抓取服务端渲染的完整 HTML 页面,避免了 CSR 中依赖 JavaScript 执行后生成内容的情况。

  1. 更好的首屏体验

由于页面内容在服务器端已经渲染好并发送到浏览器,用户可以更快地看到页面上的内容,避免了空白页面和加载延迟,提高了用户体验。

  1. 适合内容较为静态的应用

对于内容变化不频繁的应用,SSR 是一个非常合适的选择,可以减少客户端的计算压力,将大部分渲染任务交给服务器来处理。

SSR 缺点

  1. 服务器负担较重

由于页面渲染是在服务器端进行的,服务器需要处理每个请求并生成 HTML 页面。对于流量较大的网站,可能会导致服务器负载增加,影响性能和响应速度。

  1. 缺乏交互性

初始页面是静态的,虽然 SSR 可以在服务器端渲染出完整的页面,但客户端渲染的 JavaScript 代码通常需要后续加载才能实现动态交互。例如,React 的组件渲染逻辑可能需要在客户端继续执行,才会使页面变得可交互。

  1. 首次加载较慢

虽然 SSR 能加速首屏渲染,但因为需要在服务器生成 HTML,首次加载的响应时间可能会更长,尤其是服务器的处理能力不足时。

SSR 和 CSR 的对比

特性 服务端渲染 (SSR) 客户端渲染 (CSR)
首屏加载时间 快,HTML 已经在服务器渲染完成,直接发送到浏览器 慢,浏览器需要加载 JavaScript 后再渲染页面
SEO 支持 很好,搜索引擎可以抓取完整的 HTML 内容 较差,搜索引擎可能无法索引 JavaScript 生成的内容
服务器负担 较重,所有请求都需要服务器渲染页面 较轻,客户端渲染,服务器只负责提供静态资源
交互性 初始页面静态,后续需要 JavaScript 动态加载 动态交互,JavaScript 控制页面渲染和更新
复杂度 高,开发需要处理前后端渲染的一致性 低,通常只有前端开发,处理较简单