清除 React Hooks 中未安装组件的内存泄漏 [英] Cleanup memory leaks on an Unmounted Component in React Hooks

查看:23
本文介绍了清除 React Hooks 中未安装组件的内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

上下文

我将 Inertia.js 与 Laravel(后端)和 React(前端)适配器一起使用.如果你不知道惯性,它基本上是:

<块引用>

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

问题

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

<块引用>

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

在登录中(由 Inertia 创建)

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

import React, { useEffect, useState } from 'react'从../../Layouts/Auth"导入布局;{/** 其他进口 */}const 登录 = (道具) =>{const { 错误 } = usePage();const [values, setValues] = useState({email: '', password: '',});const [loading, setLoading] = useState(false);函数句柄提交(e){e.preventDefault();设置加载(真);Inertia.post(window.route('login.attempt'), values).then(() => {设置加载(假);//警告:在卸载组件的状态更新期间内存泄漏 <--------})}返回 (<Layout title="访问系统"><div><form action={handleSubmit}>{/*登录表单*/}<button type="submit">访问</button></表单>

</布局>);};导出默认登录;

现在,我知道我必须执行清理功能,因为请求的承诺是生成此警告的原因.我知道我应该使用 useEffect 但我不知道如何在这种情况下应用它.我见过值改变的例子,但如何在这种调用中做到这一点?

提前致谢.

<小时>

更新

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

import React, { useState } from 'react'从../../Layouts/Auth"导入布局;从@inertiajs/inertia-react"导入 {usePage}从@inertiajs/inertia"导入{惯性};从../../Shared/LoadingButton"导入LoadingButton;const 登录 = (道具) =>{const { 错误 } = usePage();const [values, setValues] = useState({email: '', password: '',});const [loading, setLoading] = useState(false);函数handleChange(e) {const key = e.target.id;常量值 = e.target.value;setValues(values => ({...值,[核心价值,}))}函数句柄提交(e){e.preventDefault();设置加载(真);Inertia.post(window.route('login.attempt'), values).then(() => {设置加载(假);})}返回 (<Layout title="Iniciia 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">奥普玛火花</span>

<h1 className="text-5xl 前导紧密 pb-4">商业智能<p className="text-lg">Recoge data de tus instalaciones de forma automatizada;加入信息历史与现实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">信息

<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>

<form onSubmit={handleSubmit} className={`relative ${loading ?'隐形':'可见'}`}><div className="mb-4"><label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="email">电子邮件<输入id="电子邮件"类型=文本"className = 外观-无边框圆形 w-full py-2 px-3 text-gray-700 mb-3 轮廓-none focus:border-1 focus:border-yellow-500"placeholder="介绍您的电子邮件.."名称=电子邮件"值={values.email}onChange={handleChange}/>{errors.email &&<p className="text-red-500 text-xs italic">{ errors.email[0] }</p>}

<div className="mb-6"><label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="password">康特拉塞尼亚<输入className="外观-无边框边框-红色圆形w-full py-2 px-3 text-gray-700 mb-3 outline-none focus:border-1 focus:border-yellow-500"id="密码"名称=密码"类型=密码"占位符="*********"值={值.密码}onChange={handleChange}/>{errors.password &&<p className="text-red-500 text-xs italic">{ errors.password[0] }</p>}

<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 className="w-full flex justify-center"><a href="https://optimaee.com"></a>

</布局>);};导出默认登录;

解决方案

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

<块引用>

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

在这种情况下你应该使用两个 React Hooks:useRefuseEffect.

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

<块引用>如果需要可变变量,

useRef 是首选钩子.不同于本地变量,React 确保在每个过程中返回相同的引用使成为.如果你愿意,它与类组件中的this.myVar

相同

示例:

const login = (props) =>{const _isMounted = useRef(true);//初始值 _isMounted = trueuseEffect(() => {返回 () =>{//ComponentWillUnmount 在类组件中_isMounted.current = false;}}, []);函数句柄提交(e){e.preventDefault();设置加载(真);ajaxCall = Inertia.post(window.route('login.attempt'), values).then(() => {if (_isMounted.current) {//检查始终安装的组件//继续处理 AJAX 响应... ;})}}


在同一场合,让我向您解释有关此处使用的 React Hooks 的更多信息.另外,我将比较函数式组件中的 React Hooks (React >16.8) 和类组件中的 LifeCycle.

<块引用>

useEffect :大多数副作用发生在钩子内部.副作用的示例包括:数据获取、设置订阅和手动更改 DOM React 组件.useEffect 替换了 Class Component 中的很多 LifeCycles(componentDidMount、componentDidUpate、componentWillUnmount)

 useEffect(fnc, [dependency1, dependency2, ...]);//依赖数组参数是可选的

  1. useEffect 的默认行为在第一次渲染后运行(如 ComponentDidMount) 和每次更新渲染后(如 ComponentDidUpdate) 如果您没有依赖项.就像这样:useEffect(fnc);

  2. 将依赖项数组提供给 useEffect 将改变其生命周期.在这个例子中:useEffect 将在第一次渲染后调用一次,每次计数变化

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

    useEffect(fnc, [count]);

    }

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

  4. 为了防止资源泄漏,必须在钩子的生命周期结束时处理所有内容(如 ComponentWillUnmount).例如,对于空的依赖数组,在组件卸载后将调用返回的函数.是这样的:

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

<块引用>

useRef :返回可变引用对象,其 .current 属性是初始化为传递的参数 (initialValue).返回的对象将在组件的整个生命周期内持续存在.

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

const login = (props) =>{让 _isMounted= 真;//因为局部变量不好,所以每次更新渲染时变量都会丢失并重新定义useEffect(() => {返回 () =>{_isMounted = 假;//不好}}, []);//...}

因此,结合useRefuseEffect,我们可以彻底清除内存泄漏.


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

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

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

I'm new using React, so this might be really simple to achieve but I can't figure it out by myself even though I've done some research. Forgive me if this is too dumb.

Context

I'm using Inertia.js with the Laravel (backend) and React (front-end) adapters. If you don't know Inertia, it basically:

Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.

Issue

I'm doing a simple login page that has a form that when submitted will perform a POST request to load the next page. It seems to work fine but in other pages the console shows the following warning:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

in login (created by Inertia)

The related code (I've simplified it to avoid irrelevant lines):

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;

Now, I know that I have to do a cleanup function because the promise of the request is what is generating this warning. I know that I should use useEffect but I don't know how to apply it in this case. I've seen example when a value change, but how to do it in a call of this kind?

Thanks in advance.


Update

As requested, the full code of this component:

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;

解决方案

Because it's the async promise call, so you must use a mutable reference variable (with useRef) to check already unmounted component for the next treatment of async response (avoiding memory leaks) :

Warning: Can't perform a React state update on an unmounted component.

Two React Hooks that you should use in this case : useRef and useEffect.

With useRef, for example, the mutable variable _isMounted is always pointed at the same reference in memory (not a local variable)

useRef is the go-to hook if mutable variable is needed. Unlike local variables, React makes sure same reference is returned during each render. If you want, it's the same with this.myVar in Class Component

Example :

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... ;
            }
         )
  }
}


On the same occasion, let me explain you more information about React Hooks used here. Also, I will compare React Hooks in Functional Component (React >16.8) with the LifeCycle in Class Component.

useEffect : Most side-effects happen inside the hook. Examples of side effects are : data fetching, setting up a subscription, and manually changing the DOM React components. The useEffect replaces a lot of LifeCycles in Class Component (componentDidMount, componentDidUpate, componentWillUnmount)

 useEffect(fnc, [dependency1, dependency2, ...]); // dependencies array argument is optional

  1. Default behavior of useEffect runs both after the first render (like ComponentDidMount) and after every update render (like ComponentDidUpdate) if you don't have dependencies. It's like that : useEffect(fnc);

  2. Giving array of dependencies to useEffect will change its lifecycle. In this example : useEffect will be called once after the first render and every time count changes

    export default function () { const [count, setCount] = useState(0);

    useEffect(fnc, [count]);
    

    }

  3. useEffect will run only once after the first render (like ComponentDidMount) if you put an empty array for dependency. It's like that : useEffect(fnc, []);

  4. To prevent resource leaks, everything must be disposed when lifecycle of a hook ends (like ComponentWillUnmount). For example, with the empty array of dependency, the returned function will be called after component unmounts. It's like that :

    useEffect(() => { return fnc_cleanUp; // fnc_cleanUp will cancel all subscriptions and asynchronous tasks (ex. : clearInterval) }, []);

useRef : returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

Example : with the question above, we can't use a local variable here because it will be lost and re-initiated on each update render.

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
    }
  }, []);

  // ...
}

So, with combination of useRef and useEffect, we could completely cleanup memory leaks.


The good links that you could read more about the React Hooks are :

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

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

这篇关于清除 React Hooks 中未安装组件的内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆