Skip to content
当前页导航

react

React 16、17、18 版本的对比

React16 于 2017 年发布,标志着 React 发展史上的一个转折点。它引入了多项关键特性,极大地提升了 React 的易用性和效率:

  • 更佳的组件性能: React16 优化了组件的性能,减少了不必要的重新渲染,从而大幅提升了应用程序的整体运行速度。
  • 错误边界: React16 推出了错误边界(Error Boundaries),使开发者能够在组件中捕获错误,避免它们传播到其他组件,提高了应用程序的稳定性。
  • 懒加载: React16 支持懒加载(Lazy Loading),允许开发者在需要时动态加载组件,降低了初始加载时间,提升了应用程序的性能表现。

React17 于 2020 年发布,专注于优化用户体验。

  • 并发模式: React17 引入了并发模式,使应用程序能够在不阻塞主线程的情况下更新 UI。这显著增强了应用程序的响应性,即使在处理复杂任务时也能保持流畅的交互。
  • 稳定性提升: React17 进一步提高了组件的稳定性,减少了不必要的重新渲染,提升了应用程序的整体性能和用户体验。

React18 于 2022 年发布,是 React 迄今为止最重大的更新。它带来了令人振奋的新特性

  • 自动批量更新: React18 引入了自动批量更新机制,可以自动将多个更新合并成一个批处理,大幅减少了不必要的重新渲染,极大提升了应用程序的性能。
  • Suspense: React18 引入了 Suspense API,使开发者能够在加载数据时显示占位符,提升了应用程序的加载速度和用户体验。
  • 流式渲染: React18 支持流式渲染,允许开发者将组件的更新拆分成更小的块,逐步应用到 DOM 中,大幅提升了应用程序的渲染性能。

React18 的自动批量更新是如何工作的?

自动批量更新机制在更新提交时会自动将多个更新合并成一个批处理,减少了不必要的重新渲染,从而显著提升应用程序的性能。

Suspense API 在哪些场景下很有用?

Suspense API 非常适合在加载数据时显示占位符的场景,可以提升应用程序的加载速度和用户体验。例如,在加载网络数据或大型数据集时,可以使用 Suspense API 来显示加载进度条或占位内容。

流式渲染是如何提高应用程序渲染性能的?

流式渲染将组件的更新拆分成更小的块,逐步应用到 DOM 中,避免了传统的批量渲染导致的卡顿和延迟。这极大提升了应用程序的渲染性能,即使在处理复杂组件时也能保持流畅的交互。

React18 有哪些更新?

  • 并发模式
  • 更新 render API
  • 自动批处理
  • Suspense 支持 SSR
  • startTransition
  • useTransition
  • useDeferredValue
  • useId
  • 提供给第三方库的 Hook

React 初始化加载组件会渲染两次的问题

这是因为开启了严格模式,React 在开发环境下,会刻意渲染两次组件,用来检测一些潜在问题,生产环境下只会渲染一次。 如果开发环境下,不想要渲染两次,只需要把 React.StrictMode 组件删除即可。

可检测的问题:

  • 检测已废弃的 API
  • 检测组件是否有副作用,因为 React 希望组件是一个纯函数。
tsx
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

如何避免 React 多次重复渲染?

  • 使用 PureComponent 或 React.memo
  • 使用 useCallback 和 useMemo 进行性能优化
  • 使用 React.lazy 延迟组件创建

React useState 设置相同的值,为什么会渲染组件两次

问题描述:

  1. 使用 useState 设置相同的值(基本类型),组件会重新渲染两次,第三次(包含)之后就不会触发组件重新渲染。
  2. 如果使用 useState 设置相同的值(引用类型),组件会一直重新渲染。
tsx
import { useState } from 'react'

function Login() {
  const [name, setName] = useState('joy')

  console.log('执行了 Login 组件,name 为', name)

  const handleClick = (name) => {
    setName(name)
  }

  return (
    <div>
      <button onClick={() => handleClick('tom')}>按钮</button>
    </div>
  )
}

// 控制台输出:
// 执行了 Login 组件,name 为 joy // 组件初始化渲染时
// 执行了 Login 组件,name 为 tom // 第一次点击了按钮
// 执行了 Login 组件,name 为 tom // 第二次点击了按钮
// 第三次(及之后)点击按钮,不会输出任何数据

function Childr() {
  const [form, setForm] = useState({
    name: 'joy',
    user: {
      age: 18,
      address: '深圳',
    },
  })

  console.log('执行了 Childr 组件,form 为', form)

  const handleClick = () => {
    setForm({
      ...obj,
      name: 'tom',
    })
    // setForm({
    //   ...obj,
    //   user: {
    //     ...obj.user,
    //     address: "广州",
    //     city: "cits",
    //   },
    // });
  }

  return (
    <div>
      <div>{JSON.stringify(obj)}</div>
      <div>name:{obj.name}</div>
      <div>age:{obj.user.age}</div>
      <div>address:{obj.user.address}</div>
      <button onClick={handleClick}>修改name</button>
      // <button onClick={handleClick}>修改address</button>
    </div>
  )
}

// 控制台输出:
// 执行了 Childr 组件,form 为 {"name":"joy","user":{"age":18,"address":"深圳"}} // 组件初始化渲染时
// 执行了 Childr 组件,form 为 ... // 多次执行点击事件,会多次触发组件渲染

由上代码可以看出,在正常逻辑中,如果数据没有发生变化,组件应该是不会重新渲染的,但是实际效果是渲染了。那为什么会这样呢?

简述 React 的生命周期

React 的生命周期主要分为三个阶段:MOUNTING、RECEIVE_PROPS、UNMOUNTING

  • 组件挂载时(组件状态的初始化,读取初始 state 和 props 以及两个生命周期方法,只会在初始化时运行一次)
    • componentWillMount 会在 render 之前调用(在此调用 setState,是不会触发 re-render 的,而是会进行 state 的合并。因此此时的 this.state 不是最新的,在 render 中才可以获取更新后的 this.state。)
    • componentDidMount 会在 render 之后调用
  • 组件更新时(组件的更新过程是指父组件向下传递 props 或者组件自身执行 setState 方法时发生的一系列更新的动作)
    • 组件自身的 state 更新,依次执行
      • shouldComponentUpdate(会接收需要更新的 props 和 state,让开发者增加必要的判断条件,在其需要的时候更新,不需要的时候不更新。如果返回的是 false,那么组件就不再向下执行生命周期方法。)
      • componentWillUpdate
      • render(能获取到最新的 this.state)
      • componentDidUpdate(能获取到最新的 this.state)
    • 父组件更新 props 而更新
      • componentWillReceiveProps(在此调用 setState,是不会触发 re-render 的,而是会进行 state 的合并。因此此时的 this.state 不是最新的,在 render 中才可以获取更新后的 this.state)。
      • shouldComponentUpdate
      • componentWillUpdate
      • render
      • componentDidUpdate
  • 组件卸载时
    • componentWillMount(我们常常会在组件的卸载过程中执行一些清理方法,比如事件回收、清空定时器)

新版的生命周期函数增加了 getDerivedStateFromProps,这个生命周期其实就是将传入的 props 映射到 state 中。在 React 16.4 之后,这个函数每次会在 re-render 之前调用

getDerivedStateFromProps 的作用是

  • 无条件的根据 prop 来更新内部 state,也就是只要有传入 prop 值, 就更新 state
  • 只有 prop 值和 state 值不同时才更新 state 值。

react 中组件销毁时会自动回收 ref 么?

在 React 中,组件销毁时并不会自动回收 ref。ref 是一个特殊的属性,用于引用组件实例或 DOM 元素,在组件销毁时,ref 引用的对象并不会自动被销毁,而是需要手动进行清理操作。

React 事件机制和原生 DOM 事件流有什么区别

react 中的事件是绑定到 document 上面的,

而原生的事件是绑定到 dom 上面的,

因此相对绑定的地方来说,dom 上的事件要优先于 document 上的事件执行

SetState 是同步还是异步的,setState 做了什么

在 React 中,setState()函数通常被认为是异步的,这意味着调用 setState()时不会立刻改变 react 组件中 state 的值,setState 通过触发一次组件的更新来引发重汇,多次 setState 函数调用产生的效果会合并

调用 setState 时,React 会做的第一件事情是将传递给 setState 的对象合并到组件的当前状态。这将启动一个称为和解(reconciliation)的过程。和解(reconciliation)的最终目标是以最有效的方式,根据这个新的状态来更新 UI。 为此,React 将构建一个新的 React 元素树(您可以将其视为 UI 的对象表示)。

一旦有了这个树,为了弄清 UI 如何响应新的状态而改变,React 会将这个新树与上一个元素树相比较