Очистка утечек памяти на неустановленном компоненте в React Hooks - PullRequest
19 голосов
/ 17 января 2020

Я новичок в использовании React, так что это может быть очень просто сделать, но я не могу понять это самостоятельно, хотя я провел некоторое исследование. Простите, если это слишком глупо.

Контекст

Я использую Инерция. js с Laravel (бэкэнд) и React (интерфейс) ) адаптеры. Если вы не знаете Inertia, то в основном:

Inertia. js позволяет быстро создавать современные одностраничные приложения React, Vue и Svelte, используя classi c маршрутизацию на стороне сервера и контроллеры.

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, но я не знаю, как применить его в этом случае. Я видел пример, когда значение изменяется, но как это сделать в вызове такого рода?

Заранее спасибо.


Обновление

По запросу , полный код этого компонента:

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;

Ответы [ 2 ]

22 голосов
/ 28 января 2020

Поскольку это асиновый вызов обещания c, поэтому вы должны использовать изменчивую переменную ref (с useRef) , чтобы проверить уже отключенный компонент для следующей обработки ответа asyn c (во избежание утечек памяти) ):

Предупреждение. Невозможно выполнить обновление состояния React для неустановленного компонента.

Два элемента React Hook, которые следует использовать в этом случае: useRef и useEffect.

Например, при useRef изменяемая переменная _isMounted всегда указывает на одну и ту же ссылку в памяти (не локальная переменная)

useRef - это крюк go, если требуется переменная переменная. В отличие от локальных переменных, 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 Hooks в Функциональном Компоненте (версия React> 16.8) с LifeCycle в Компоненте Класса.

useEffect : Большинство побочных эффектов происходят внутри крючка. Примерами побочных эффектов являются: выборка данных, настройка подписки и изменение DOM вручную в компонентах React. UseEffect заменяет множество LifeCycles в компоненте класса (componentDidMount, componentDidUpate, componentWillUnmount)

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

1) Поведение по умолчанию для useEffect запускается как после первого рендеринга (например, ComponentDidMount) и после каждого обновления визуализируйте (например, ComponentDidUpdate) , если у вас нет зависимостей. Это так: useEffect(fnc);

2) Предоставление массива зависимостей для использованияEffect изменит его жизненный цикл. В этом примере: useEffect будет вызываться один раз после первого рендеринга, и каждый раз при изменении счетчика

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

   useEffect(fnc, [count]);
}

3) useEffect будет запускаться только один раз после первого рендеринга (например, ComponentDidMount) , если вы поставить пустой массив для зависимости. Это так: useEffect(fnc, []);

4) Чтобы предотвратить утечку ресурсов, все должно быть утилизировано, когда жизненный цикл ловушки заканчивается (как ComponentWillUnmount) . Например, с пустым массивом зависимостей, возвращаемая функция будет вызываться после размонтирования компонента. Это так:

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

useRef : возвращает изменяемый объект ref , свойство .current которого инициализируется переданным аргумент (initialValue). Возвращенный объект будет сохраняться в течение всего времени жизни компонента.

Пример: с вопросом выше, мы не можем использовать локальную переменную здесь, потому что она будет потеряна и повторно инициируется при каждом обновлении render.

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

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

  // ...
}

Итак, с комбинацией useRef и useEffect мы могли бы полностью устранить утечки памяти.


Хорошо ссылки, которые вы можете прочитать больше о React Hooks:

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

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

0 голосов
/ 17 января 2020

Вы можете использовать метод cancelActiveVisits Inertia для отмены активного visit в useEffect ловушке очистки.

Таким образом, при этом вызове активный visit будет отменен и состояние будет не обновляться.

useEffect(() => {
    return () => {
        Inertia.cancelActiveVisits(); //To cancel the active visit.
    }
}, []);

если запрос Inertia будет отменен, он вернет пустой ответ, поэтому вам нужно добавить дополнительную проверку для обработки пустого ответа. Добавьте также блок catch для обработки любых потенциальных ошибок.

 function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    Inertia.post(window.route('login.attempt'), values)
      .then(data => {
         if(data) {
            setLoading(false);
         }
      })
      .catch( error => {
         console.log(error);
      });
  }

Альтернативный способ (обходной путь)

Вы можете использовать useRef, чтобы сохранить состояние Компонент и на основе этого вы можете обновить state.

Проблема:

Сражение показывает, потому что handleSubmit пытается обновить состояние Компонент, даже если компонент имеет размонтирован из DOM.

Решение:

Установите флаг для хранения статуса component, если component равно mounted, тогда значение flag будет true и если component равно unmounted, значение флага будет ложным. На основании этого мы можем обновить state. Для статуса флага мы можем использовать useRef для хранения ссылки.

useRef возвращает изменяемый объект ref, свойство .current которого инициализируется переданным аргументом (initialValue). Возвращенный объект будет сохраняться в течение всего времени жизни компонента. В useEffect возвращает функцию, которая будет устанавливать состояние компонента, если он не подключен.

И затем в useEffect в функции очистки мы можем установить флаг на false.

Функция очистки useEffecr

Хук useEffect позволяет использовать функцию очистки. Каждый раз, когда эффект больше не действует, например, когда компонент, использующий этот эффект, отключается, эта функция вызывается для очистки всего. В нашем случае мы можем установить флаг в false.

Пример:

let _componentStatus.current =  useRef(true);
useEffect(() => {
    return () => {
        _componentStatus.current = false;
    }
}, []);

И в handleSubmit мы можем проверить, смонтирован ли компонент или нет и обновите состояние, основываясь на этом.

function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    Inertia.post(window.route('login.attempt'), values)
        .then(() => {
            if (_componentStatus.current) {
                setLoading(false);
            } else {
                _componentStatus = null;
            }
        })
}

В противном случае установите _componentStatus в null, чтобы избежать любых утечек памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...