发布于 2026-01-06 0 阅读
0

React DEV 全球展示挑战赛中的 StrictMode 是什么?由 Mux 呈现:展示你的项目!

React 中的 StrictMode 是什么?

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

随着经验的积累,我们的编码实践和设计模式也在不断改进。React 也不例外。

React 也经历了许多变革,随着它的发展,过去一些被认为是好的做法已经不再适合未来的发展路线图。

v16 版本发布时发生了一项重大变化,它基于 React Fiber 架构进行了重写。主要改进之处在于调度(即决定何时执行某项工作,同时考虑到动画、UI 更新等不同任务的优先级)。

大约在同一时间,React 中新增了 Context API。

此外,为了在未来的版本中提供并发模式(将渲染阶段拆分为多个部分),我们进行了许多更改。这些更改包括引入 React Hooks、弃用某些生命周期方法等等。

本文将探讨StrictModeReact 中所有已弃用的模式,以帮助我们识别它们。

什么是 React.StrictMode?我们该如何使用它?

React.StrictMode是一个用于突出显示应用程序中潜在问题的工具。它的工作原理是将其渲染为一个组件,该组件可以封装应用程序的一部分或整个应用程序。StrictMode它不会在 DOM 中渲染任何可见元素,但会在开发模式下启用某些检查并提供警告。

注意:StrictMode生产模式下不运行任何检查或显示警告。

您可以React.StrictMode像这样为整个应用程序启用此功能:

import ReactDOM from 'react-dom';
import React from 'react';
import App from './App';

ReactDOM.render(
   <React.StrictMode>
      <App />
   <React.StrictMode>,
   document.getElementById("app")
);
Enter fullscreen mode Exit fullscreen mode

您也可以通过将其包装在应用程序的一部分中来启用它<React.StrictMode>

StrictModeReact v17 版本支持以下功能:

  • 识别遗留字符串引用。

  • 检测到已弃用的findDOMNode方法。

  • 检测到对旧版 Context API 的使用。

  • 检测 React 已弃用的不安全生命周期方法。

  • 检测 React 组件中意外的副作用。


1. 识别遗留字符串引用

React 的初始版本中使用字符串来赋值引用。然而,正如 Dan Abramov 在这个 Github Issue中指出的那样,这种方式存在许多问题

“这要求 React 跟踪当前正在渲染的组件(因为它无法猜测this)。这使得 React 的速度稍慢一些。”

它不像大多数人预期的那样使用“渲染回调”模式(例如),因为上述原因,<List renderRow={this.renderRow} />ref 会被放置在。List

它不具备可组合性,也就是说,如果一个库对传入的子元素添加了一个引用,用户就无法再添加另一个引用。回调引用则完全可以组合使用。

由于这些原因以及其他诸多原因,例如 TypeScript 中引用类型需要转换等问题,人们为类组件引入了更好的替代方案:

  • 回调引用

  • React.createRef


2. 检测已弃用的findDOMNode方法

ReactDOM.findDOMNode方法之前用于根据类实例获取 DOM 节点。findDOMNode可以通过直接向 DOM 元素添加 ref 而不是类实例来避免使用此方法。

API存在两个主要问题findDOMNode

  • 这只会返回类组件实例中的第一个子组件。但是,随着 v16 版本中 Fragment 的引入,您可以从组件实例中返回多个元素,这可能会导致问题,因为您可能希望定位到所有元素的包装器,或者从返回的元素列表中选择特定元素。

  • findDOMNodeAPI 是仅请求式的(即,它会在被调用时进行评估并返回结果)。例如,如果子元素中渲染的元素发生了条件性变化,父元素可能并不知道这一点。

另一种方法findDOMNode是使用React.forwardRef并传递指向子组件中所需元素的 ref,或者通过单独的名称(例如innerRef)传递 ref,并在子组件的 props 中使用它来设置所需元素的 ref。


3. 传统上下文 API

React 16.3 版本引入了新的 Context API。在此之前,使用的是容易出错的旧 API,如果父组件层级结构中的某个组件通过实现 `onResources` 停止重新渲染子元素,则会导致消费者无法更新shouldComponentUpdate

尽管 React 在 v16.x 中继续支持旧 API,但StrictMode会通过显示警告来指出旧 Context API 的使用情况,以便将其迁移到最新版本。


4. 检测不安全的生命周期方法

在 React v16.3.0 中,React API 进行了一些突破性的变化。其中一项变化是弃用了诸如 `return`、`return` 和 `return` 之类的生命周期方法componentWillMountcomponentWillReceiveProps同时componentWillUpdate还添加了新的生命周期方法,例如 `return`getDerivedStateFromProps和 `return` getSnapShotBeforeUpdate

尽管这些生命周期方法在 React 的后续版本中仍然可用,并且已经重命名并UNSAFE_添加了前缀,但 React 可能会在未来的版本中完全移除它们。

为什么这些生命周期方法被弃用了?

要理解这一点,我们首先必须知道 React 通常分两个阶段进行:

渲染阶段:在此阶段,React 会检查需要对 DOM 进行哪些更改。Reactrender在此阶段会调用一个函数,并将结果与​​之前的渲染进行比较。渲染阶段的生命周期包括 `on` componentWillMountcomponentWillReceiveProps`on`、componentWillUpdate`on` 和render`on`。

提交阶段:componentDidMount这是 React 实际将更改提交到 DOM 并调用提交阶段生命周期(例如 `requests.commit()`和 ` requests.commit()`)的阶段componentDidUpdate

提交阶段很快,但渲染阶段可能很慢。为了优化渲染,并符合并发模式的理念,React 决定将渲染过程拆分成多个部分,通过暂停和恢复来避免阻塞浏览器。

因此,当这样做时,渲染阶段的生命周期可能会被多次调用,如果这些调用包含副作用或不正确的做法,则可能导致应用程序行为不一致。此外,其中一些生命周期还会助长不良的开发实践。这些包括:

  • 组件将挂载

  • 组件将接收属性

  • 组件将更新

让我们来看其中的一些做法。

在 componentWillMount 中调用 setState

// Incorrect
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  componentWillMount() {
    this.setState({
      selectedTheme: this.props.defaultTheme,
    })
  }

  // Rest of code
}

// Correct approach
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
        selectedTheme: props.defaultTheme,
    };
  }

  // Rest of code
}
Enter fullscreen mode Exit fullscreen mode

如上文代码片段所示,componentWillMount它用于在初始渲染之前设置状态,但可以通过在构造函数中设置初始状态或将其state作为类属性来轻松重构。

componentWillMount 中的异步请求

异步获取请求componentWillMount对于服务器端渲染和即将推出的并发模式都会带来问题。对于服务器端渲染,获取到的数据componentWillMount将不会被使用。对于异步渲染,获取请求可能会被多次发出。

// Incorrect way to fetchData
class ExampleComponent extends React.Component {
  state = {
     data: []
  }
  componentWillMount() {
    fetchData().then(res => {
      this.setState({
        data: res.data
      });
    })
  }

  // Rest of the code
}

// Correct way to fetchData and update state
class ExampleComponent extends React.Component {
  state = {
     data: [],
     isLoading: true,
  }
  componentDidMount() {
    fetchData().then(res => {
      this.setState({
        data: res.data,
        isLoading: false
      });
    })
  }

  // Rest of the code
}

Enter fullscreen mode Exit fullscreen mode

人们普遍误以为componentWillMount在初始渲染之前获取的任何数据都可用。事实并非如此,您应该使用加载状态来避免在初始渲染中使用这些数据,而是通过 API 调用来获取数据componentDidMount

在 componentWillMount 中添加订阅或监听器

添加订阅/监听器时存在两个问题componentWillMount

  • 使用服务器端渲染时,该componentWillUnmount函数不会在服务器上调用,因此不会进行清理,可能会导致内存泄漏。

  • 使用异步渲染时,可以附加多个订阅,因为渲染阶段生命周期可能会被多次调用。

// Incorrect way
class ExampleComponent extends React.Component {
  componentWillMount() {
    this.unlisten = this.props.dataSource.listen(
      this.handleDataSourceChange
    );
  }

  componentWillUnmount() {
   this.unlisten();
  }

  handleDataSourceChange = data => {};
}

// Correct way
class ExampleComponent extends React.Component {
  componentDidMount() {
    this.unlisten = this.props.dataSource.listen(
      this.handleDataSourceChange
    );
  }

  componentWillUnmount() {
   this.unlisten();
  }

  handleDataSourceChange = data => {};
}
Enter fullscreen mode Exit fullscreen mode

添加和删​​除监听器的正确方法是将 `add` 和componentDidMount`remove`componentWillUnmount生命周期方法配对使用。

更新状态或调用属性更改的副作用

以前,componentWillReceiveProps生命周期函数用于在父组件的属性发生变化时更新子组件的状态或调用副作用。虽然这种做法本身并没有什么问题,但开发者们误以为生命周期函数只会在属性更新时被调用。

但是,每当父级属性重新渲染时,它都会被调用。

因此,如果在比较先前和当前的属性之后没有正确执行,任何函数调用或状态更新都可能出现不一致的行为。

在更新之前读取 DOM 属性

有时,您可能需要在更新之前保存某些 DOM 属性(例如滚动位置),以便在应用更新时将其还原,从而防止在添加或删除新项目时,用户当前可见的项目超出视图范围。

以前,你会在生命周期方法中执行此操作。但是,使用异步渲染时, `get_scroll_location()` 和 `get_scroll_location()` 的调用componentWillUpdate时间之间可能存在时间间隔,如果用户以某种方式与 DOM 交互并实际改变了滚动位置(例如调整窗口大小或滚动更多内容),则可能会导致不一致。因此,建议使用 `get_scroll_location()` 作为 `get_scroll_location()` 的替代方案,因为它会在 DOM 发生更改之前立即调用。componentWillUpdatecomponentDidUpdategetSnapshotBeforeUpdatecomponentWillUpdate

现在我们已经了解了一些删除这些用法的原因,让我们回到正题。

我们可能会想:“为什么还需要一些工具来指出哪些功能不安全呢?我们可以直接搜索并按照推荐的做法更新它们。”

虽然你的做法是正确的,而且你可以在自己的代码库中这样做,但是你将无法轻易识别出你代码库中作为依赖项使用的库中存在的不安全StrictMode生命周期。它也能帮助你指出这些问题,以便你可以更新它们(或者在最新版本不兼容的情况下用替代方案替换它们)。


5. 检测意外副作用

正如我们在上一节中提到的,React 为了优化即将推出的并发模式下的渲染阶段,决定将渲染阶段拆分。因此,渲染阶段的生命周期可能会被多次调用,如果其中使用了副作用,则会导致一些意想不到的行为。

在最新版本的 React 中,这些功能包括:

  • constructor

  • getDerivedStateFromProps

  • shouldComponentUpdate

  • render

  • setState类组件和函数组件中的更新函数

  • 传递给useMemo, useState,的函数useReducer

虽然副作用是不确定的,但StrictMode通过两次调用上述函数,可以稍微提高其确定性,从而帮助开发者更好地理解副作用。这样,如果渲染阶段函数中任何副作用编写错误,由于其明显的矛盾之处,即使在开发模式下也能发现问题。

例如,如果在函数中建立 WebSocket 连接constructor,则在开发模式下进行两次调用constructor可以帮助更容易地发现问题,因为会建立两个连接。


要点总结

  • React.StrictMode可以对应用程序的部分或全部启用。

  • 它仅在开发模式下运行,以提供有关旧式引用使用、已弃用的findDOMNode方法、旧式上下文 API、不安全的生命周期和意外副作用的警告。

  • StrictMode这样可以有意地对渲染阶段生命周期和函数进行两次调用,以便更容易发现这些函数中实现的意外副作用。

感谢阅读。

如果您觉得这篇文章有用且内容丰富,请不要忘记点赞并分享给您的朋友和同事。

如果您有任何建议,请随时留言。

关注我的推特账号,获取更多网页开发内容。

文章来源:https://dev.to/shubhamreacts/what-is-strictmode-in-react-4if