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

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

问题描述

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

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.

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

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

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

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

问题

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

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:

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

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.

登录(由Inertia创建)

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;

现在,我知道我必须执行清除功能,因为请求的承诺是生成此警告的原因.我知道我应该使用 useEffect ,但是在这种情况下我不知道如何应用它.我已经看到了一个值更改的示例,但是如何在这种调用中做到这一点呢?

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?

谢谢.

根据要求,提供此组件的完整代码:

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;

推荐答案

由于这是异步promise调用,因此您必须使用可变引用变量(带有useRef)来检查已经卸载的组件异步响应的下一个处理方法(避免内存泄漏):

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

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

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

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

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

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

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

如果需要可变变量,则

useRef 是该钩子.不像本地变量,React确保在每个变量期间都返回相同的引用使成为.如果需要,与类组件中的 this.myVar相同

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

示例:

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进行比较.


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 :大多数副作用都发生在钩子内部.副作用的示例包括:数据获取,设置订阅以及手动更改DOM React组件.useEffect替换了类Component中的许多生命周期(componentDidMount,componentDidUpate,componentWillUnmount)

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. useEffect的默认行为在第一次渲染(例如ComponentDidMount)之后和每次更新渲染(例如ComponentDidUpdate)之后都运行(如果您没有依赖性).就像这样: useEffect(fnc);

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

将依赖项数组提供给useEffect将更改其生命周期.在此示例中:useEffect将在第一个渲染之后被调用一次,并且每次计数都改变

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

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

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

useEffect(fnc, [count]);

}

useEffect将在第一个渲染(如ComponentDidMount)之后仅运行一次.就像这样: useEffect(fnc,[]);

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

为防止资源泄漏,必须在挂钩的生命周期(如ComponentWillUnmount)结束时处置所有东西.例如,对于空的依赖项数组,将在组件卸载后调用返回的函数.就像这样:

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(()=> {返回fnc_cleanUp;//fnc_cleanUp将取消所有订阅和异步任务(例如:clearInterval)},[]);

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

useRef :返回一个可变的ref对象,该对象的 .current 属性为初始化为传递的参数(initialValue).返回的对象将在组件的整个生命周期内保持不变.

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

  // ...
}

因此,结合使用 useRef useEffect ,我们可以完全清除内存泄漏.

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

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

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天全站免登陆