清理 React Hooks 中未安装组件的内存泄漏

2024-03-29

我是 React 的新手,所以这可能很容易实现,但即使我做了一些研究,我自己也无法弄清楚。如果这太愚蠢了,请原谅我。

Context

我在用着惯性.js https://inertiajs.com/使用 Laravel(后端)和 React(前端)适配器。如果你不了解惯性,它基本上是:

Inertia.js 可让您快速构建现代单页 React、Vue 和 使用经典服务器端路由和控制器的 Svelte 应用程序。

Issue

我正在做一个简单的登录页面,其中有一个表单,提交后将执行 POST 请求来加载下一页。它似乎工作正常,但在其他页面中控制台显示以下警告:

警告:无法对已卸载的组件执行 React 状态更新。 这是一个空操作,但它表明应用程序中存在内存泄漏。 要修复此问题,请取消 useEffect 中的所有订阅和异步任务 清理功能。

在登录中(由 Inertia 创建)

相关代码(我对其进行了简化以避免不相关的行):

import React, { useEffect, useState } from 'react'
import Layout from "../../Layouts/Auth";

{/** other imports */}

    const login = (props) => {
      const { errors } = usePage();

      const [values, setValues] = useState({email: '', password: '',});
      const [loading, setLoading] = useState(false);

      function handleSubmit(e) {
        e.preventDefault();
        setLoading(true);
        Inertia.post(window.route('login.attempt'), values)
          .then(() => {
              setLoading(false); // Warning : memory leaks during the state update on the unmounted component <--------
           })                                   
      }

      return (
        <Layout title="Access to the system">
          <div>
            <form action={handleSubmit}>
              {/*the login form*/}
  
              <button type="submit">Access</button>
            </form>
          </div>
        </Layout>
      );
    };

    export default login;

现在,我知道我必须执行清理功能,因为请求的承诺是生成此警告的原因。我知道我应该使用useEffect但我不知道如何在这种情况下应用它。我见过值更改时的示例,但是如何在此类调用中执行此操作?


Update

根据要求,该组件的完整代码:

import React, { useState } from 'react'
import Layout from "../../Layouts/Auth";
import { usePage } from '@inertiajs/inertia-react'
import { Inertia } from "@inertiajs/inertia";
import LoadingButton from "../../Shared/LoadingButton";

const login = (props) => {
  const { errors } = usePage();
  
  const [values, setValues] = useState({email: '', password: '',});
  
  const [loading, setLoading] = useState(false);
  
  function handleChange(e) {
    const key = e.target.id;
    const value = e.target.value;
    
    setValues(values => ({
      ...values,
      [key]: value,
    }))
  }
  
  function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    Inertia.post(window.route('login.attempt'), values)
      .then(() => {
        setLoading(false);
      })
  }
  
  return (
    <Layout title="Inicia sesión">
      <div className="w-full flex items-center justify-center">
        <div className="w-full max-w-5xl flex justify-center items-start z-10 font-sans text-sm">
          <div className="w-2/3 text-white mt-6 mr-16">
            <div className="h-16 mb-2 flex items-center">                  
              <span className="uppercase font-bold ml-3 text-lg hidden xl:block">
                Optima spark
              </span>
            </div>
            <h1 className="text-5xl leading-tight pb-4">
              Vuelve inteligente tus operaciones
            </h1>
            <p className="text-lg">
              Recoge data de tus instalaciones de forma automatizada; accede a información histórica y en tiempo real
              para que puedas analizar y tomar mejores decisiones para tu negocio.
            </p>
            
            <button type="submit" className="bg-yellow-600 w-40 hover:bg-blue-dark text-white font-semibold py-2 px-4 rounded mt-8 shadow-md">
              Más información
            </button>
          </div>
          
        <div className="w-1/3 flex flex-col">
          <div className="bg-white text-gray-700 shadow-md rounded rounded-lg px-8 pt-6 pb-8 mb-4 flex flex-col">
            <div className="w-full rounded-lg h-16 flex items-center justify-center">
              <span className="uppercase font-bold text-lg">Acceder</span>
            </div>
  
            <form onSubmit={handleSubmit} className={`relative ${loading ? 'invisible' : 'visible'}`}>
              
              <div className="mb-4">
                <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="email">
                  Email
                </label>
                <input
                  id="email"
                  type="text"
                  className=" appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 outline-none focus:border-1 focus:border-yellow-500"
                  placeholder="Introduce tu e-mail.."
                  name="email"
                  value={values.email}
                  onChange={handleChange}
                />
                {errors.email && <p className="text-red-500 text-xs italic">{ errors.email[0] }</p>}
              </div>
              <div className="mb-6">
                <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="password">
                  Contraseña
                </label>
                <input
                  className=" appearance-none border border-red rounded w-full py-2 px-3 text-gray-700 mb-3 outline-none focus:border-1 focus:border-yellow-500"
                  id="password"
                  name="password"
                  type="password"
                  placeholder="*********"
                  value={values.password}
                  onChange={handleChange}
                />
                {errors.password && <p className="text-red-500 text-xs italic">{ errors.password[0] }</p>}
              </div>
              <div className="flex flex-col items-start justify-between">
                <LoadingButton loading={loading} label='Iniciar sesión' />
                
                <a className="font-semibold text-sm text-blue hover:text-blue-700 mt-4"
                   href="#">
                  <u>Olvidé mi contraseña</u>
                </a>
              </div>
              <div
                className={`absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center ${!loading ? 'invisible' : 'visible'}`}
              >
                <div className="lds-ellipsis">
                  <div></div>
                  <div></div>
                  <div></div>
                  <div></div>
                </div>
              </div>
            </form>
          </div>
          <div className="w-full flex justify-center">
            <a href="https://optimaee.com">
            </a>
          </div>
        </div>
        </div>
      </div>
    </Layout>
  );
};

export default login;

因为它是异步 Promise 调用,所以你必须使用可变引用变量(使用 useRef)检查已卸载的组件以进行下一步的异步响应处理(避免内存泄漏):

警告:无法对已卸载的组件执行 React 状态更新。

在这种情况下您应该使用两个 React Hooks:useRef and useEffect.

With useRef,例如,可变变量_isMounted总是指向内存中的同一个引用(不是局部变量)

useRef如果需要可变变量,则为首选钩子。与本地不同 变量,React 确保在每个变量期间返回相同的引用 使成为。如果你愿意的话,也是一样的类组件中的 this.myVar

例子 :

const login = (props) => {
  const _isMounted = useRef(true); // Initial value _isMounted = true

  useEffect(() => {
    return () => { // ComponentWillUnmount in Class Component
        _isMounted.current = false;
    }
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    ajaxCall = Inertia.post(window.route('login.attempt'), values)
        .then(() => {
            if (_isMounted.current) { // Check always mounted component
               // continue treatment of AJAX response... ;
            }
         )
  }
}

同时,让我向您解释有关此处使用的 React Hooks 的更多信息。另外,我还将比较功能组件(React >16.8)中的 React Hooks 与类组件中的 LifeCycle。

使用效果:大多数副作用发生在钩子内部。副作用的示例包括:数据获取、设置订阅以及手动更改 DOM React 组件。 useEffect 替换了 Component 类中的很多 LifeCycle(componentDidMount、componentDidUpate、componentWillUnmount)

 useEffect(fnc, [dependency1, dependency2, ...]); // dependencies array argument is optional
  1. useEffect 的默认行为在第一次渲染后运行(如 ComponentDidMount)并在每次更新渲染后(如 ComponentDidUpdate)如果你没有依赖关系。就像那样 :useEffect(fnc);

  2. 为 useEffect 提供一系列依赖项将改变其生命周期。在此示例中:useEffect 将在第一次渲染后以及每次计数更改时调用一次

    导出默认函数 () { const [count, setCount] = useState(0);

    useEffect(fnc, [count]);
    

    }

  3. useEffect 将在第一次渲染后仅运行一次(如 ComponentDidMount)如果您放置一个空数组作为依赖项。就像那样 :useEffect(fnc, []);

  4. 为了防止资源泄漏,当钩子的生命周期结束时,所有的东西都必须被处理掉(如 ComponentWillUnmount)。例如,如果依赖项为空数组,则返回的函数将在组件卸载后调用。就像那样 :

    使用效果(()=> { 返回fnc_cleanUp; // fnc_cleanUp 将取消所有订阅和异步任务(例如:clearInterval) }, []);

useRef:返回可变的引用对象 whose .current财产是 初始化为传递的参数 (initialValue)。返回的对象 将在组件的整个生命周期中持续存在。

示例:对于上面的问题,我们不能在这里使用局部变量,因为它会丢失并在每次更新渲染时重新启动。

const login = (props) => {
  let _isMounted= true; // it isn't good because of a local variable, so the variable will be lost and re-defined on every update render

  useEffect(() => {
    return () => {
        _isMounted = false;  // not good
    }
  }, []);

  // ...
}

所以,结合useRef and 使用效果,我们可以彻底清理内存泄漏。


您可以阅读有关 React Hooks 的更多内容的好链接是:

[EN] https://medium.com/@sdolidze/the-iceberg-of-react-hooks-af0b588f43fb https://medium.com/@sdolidze/the-iceberg-of-react-hooks-af0b588f43fb

[FR] https://blog.soat.fr/2019/11/react-hooks-par-lexemple/ https://blog.soat.fr/2019/11/react-hooks-par-lexemple/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

清理 React Hooks 中未安装组件的内存泄漏 的相关文章

随机推荐

  • 是否可以查询ExpandoObject列表?

    我想知道是否可以使用常规 LINQ 查询 ExpandoObject 原因是我有动态 ExpandoObject 但我需要进行一些查询才能进一步传递 它有一些始终保留的属性 例如Id Notes还有一些我无法控制的动态属性 这是我的列表的样
  • 在 PowerPoint 中插入、裁剪和调整图像大小,每张现有幻灯片一张

    这是我创建的用于插入图像和调整图像大小的代码 每张幻灯片一个 我无法裁剪从文件位置插入的图像 过程应该是 将图像从文件位置插入到现有幻灯片中 将图像裁剪为所需的尺寸 将图像调整为所需的大小 注意 我的一个文件中有大约 40 张图像 我需要将
  • 什么是依赖类型?

    有人可以向我解释依赖类型吗 我对 Haskell Cayenne Epigram 或其他函数式语言缺乏经验 因此您可以使用的术语越简单 我就越感激 考虑一下 在所有像样的编程语言中 您都可以编写函数 例如 def f arg result
  • 在 Rails 查询中提取关联模型的属性

    在我的 Rails 应用程序中 收藏品有很多projects 并且项目有很多steps 我想获取集合项目中步骤的所有 id 我想知道是否可以在一个查询中完成这一切 例如 我知道我可以执行以下操作 step ids collection pr
  • Meteor 构建内存不足

    我正在尝试构建我的流星应用程序 并且不断遇到以下错误 这不是我第一次构建该应用程序 直到昨天的构建为止一切正常 我已经尝试过 正如 this SO post 1 中的一个答案中所建议的 但它没有帮助 usr bin env node max
  • SAPUI5:如何过滤具有2个或更多值的数据

    我目前正在 SAPUI5 中尝试一些东西 并且我已经实现了一个非常简单的搜索 如下所示 var filters var query evt getParameter query if query query length gt 0 var
  • Android:从服务中关闭屏幕

    我正在尝试找出如何从服务中关闭屏幕 我已经读过this https stackoverflow com questions 1875669 calling hidden api in android to turn screen off但我
  • 如何使用实体框架将存储过程的结果映射到具有不同命名参数的实体

    我正在尝试使用实体框架创建一个基本示例 以将 SQL Server 存储过程的输出映射到 C 中的实体 但该实体具有不同 友好 的名称参数 而不是更神秘的名称 我还尝试使用 Fluent 即非 edmx 语法来做到这一点 什么有效 存储过程
  • -XX:MaxPermSize 带或不带 -XX:PermSize

    我们遇到了一个Java lang OutOfMemoryError 永久代空间错误并查看 tomcat JVM 参数 除了 Xms and Xmx我们还指定了参数 XX MaxPermSize 128m 经过一些分析后 我可以看到 Perm
  • 将 Leaflet 与 Ionic2 typescript 一起使用

    我是新来的Ionic2 and Angular2 with 打字稿我想为 iOS 和 Android 构建一个移动应用程序 下一步我想包括一张地图并找到Leaflet 在 GoogleMaps 和 OSM 之间轻松更改 所以 我的问题从安装
  • MSBuild条件编译

    我有一个 Web 服务类 框架的其余部分依赖它来获取其数据 但是 Web 服务类需要根据其所处的环境具有不同的方法属性 例如 SoapDocumentMethodAttribute https example public string T
  • 在内存中运行 Docker?

    据我了解 Docker 使用内存映射文件从镜像启动 由于我可以一遍又一遍地执行此操作 并且据我记得并行启动同一图像的不同实例 我猜 docker 抽象了文件系统并将更改存储在其他地方 我想知道 docker 是否可以配置 或者默认情况下 以
  • 从一个 Visual Studio 实例调试程序的多个副本

    我有一个 pre alpha GUI 程序内部测试 http en wikipedia org wiki Eat one 27s own dog food并希望在调试器下运行 当出现问题时 但我不想为应用程序的每个实例启动 Visual S
  • 如何检测网格单元格中的点击并更改其颜色?

    对 python 相当陌生 并试图重新创建我在学校玩的游戏 对于这个游戏 我需要随机产生这些炸弹 如果击中 这些炸弹会将您的银行帐户减少到零 并将方块的炸弹方块的颜色更改为红色 而安全的则变为绿色 我遇到的问题是 我想将炸弹方块的颜色改为红
  • 验证是否有可用网络连接的最简单方法是什么?

    我是 c net 开发的新手 但我已经为我公司的一小部分资产编写了一个股票跟踪应用程序 我还在 SQL 2000 中设置了它连接的数据库 目前 当网络连接可用时 它可以正常工作 但我想扩展它 以便在没有连接时使用 首先 我需要知道是否有可用
  • 如何在 LWUIT 选项卡上显示表单屏幕?

    我在表单上有列表项 当用户单击选项卡时 我必须在选项卡上显示该表单 之后如何将该表单添加到选项卡form show 或者之前 我需要使用表单屏幕默认显示第一个选项卡 您可以在选项卡中显示表单 形式也是一个组件 Form frmObj new
  • 设置 Text().frame(maxWidth: .infinity) 后如何保持 Text 作为前导对齐

    这可能是一个简单的案例 但我找不到解决方案 这是我的代码 使用 GeometryReader 设置 SwiftUI 视图大小的相对布局 如 Image 问题出现在最后一个VStack上 我希望文本的背景转到VStack的右端 因此 我将其设
  • OG:类型文章与网站,用于非文章的单个网页

    我正在寻求有关 og type 文章与网站对于单个网页的使用的澄清 该网页在技术上不是 文章 但也不代表整个网站 文档似乎矛盾 From https developers facebook com docs opengraphprotoco
  • 使用正则表达式从内容中删除 HTML 注释 [重复]

    这个问题在这里已经有答案了 我正在将页面内容放入变量中 content 我需要使用正则表达式从 content 中删除 HTML 注释 我尝试了以下代码 它无法正常工作 content preg replace content 看起来你错过
  • 清理 React Hooks 中未安装组件的内存泄漏

    我是 React 的新手 所以这可能很容易实现 但即使我做了一些研究 我自己也无法弄清楚 如果这太愚蠢了 请原谅我 Context 我在用着惯性 js https inertiajs com 使用 Laravel 后端 和 React 前端