Reaksiyon Kancalarındaki Takılmamış Bileşende temizleme belleği sızıntıları


19

React kullanarak yeniyim, bu yüzden bunu başarmak gerçekten kolay olabilir, ancak biraz araştırma yapmama rağmen bunu kendim anlayamıyorum. Bu çok aptalsa beni affet.

bağlam

Ben kullanıyorum Inertia.js laravel (arka uç) ve (ön uç) adaptörleri yanıt verin. Atalet bilmiyorsanız, temel olarak:

Inertia.js, klasik sunucu tarafı yönlendirme ve denetleyicileri kullanarak hızlı bir şekilde modern tek sayfalık React, Vue ve Svelte uygulamaları oluşturmanıza olanak tanır.

Konu

Gönderildiğinde bir sonraki sayfayı yüklemek için bir POST isteği gerçekleştirecek bir forma sahip basit bir giriş sayfası yapıyorum. İyi çalışıyor gibi görünüyor, ancak diğer sayfalarda konsol aşağıdaki uyarıyı gösteriyor:

Uyarı: Takılmayan bir bileşen üzerinde bir Reaksiyon durumu güncellemesi gerçekleştirilemiyor. Bu bir işlemdir, ancak uygulamanızda bir bellek sızıntısı olduğunu gösterir. Düzeltmek için bir useEffect temizleme işlevindeki tüm abonelikleri ve eşzamansız görevleri iptal edin.

giriş (Inertia tarafından oluşturuldu)

İlgili kod (Alakasız satırlardan kaçınmak için basitleştirdim):

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;

Şimdi, bir temizleme işlevi yapmak zorunda olduğumu biliyorum, çünkü isteğin vaadi bu uyarıyı üreten şeydir. Kullanmam gerektiğini biliyorum useEffectama bu durumda nasıl uygulayacağımı bilmiyorum. Bir değer değiştiğinde örnek gördüm, ama bu tür bir çağrıda nasıl yapılır?

Şimdiden teşekkürler.


Güncelleme

İstendiği gibi, bu bileşenin tam kodu:

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;

@Sohail Bileşenin tam kodunu ekledim
Kenny Horna

Basitçe kaldırmayı denediniz .then(() => {})mi?
Guerric P

Yanıtlar:


22

Eşzamansız vaat çağrısı olduğundan, asenkron yanıtın bir sonraki tedavisi için (bellek sızıntılarını önleme) zaten monte edilmemiş bileşeni kontrol etmek için değiştirilebilir bir ref değişkeni (useRef ile) kullanmalısınız:

Uyarı: Takılmayan bir bileşen üzerinde bir Reaksiyon durumu güncellemesi gerçekleştirilemiyor.

Bu durumda kullanmanız gereken iki Reaksiyon Kancası: useRefve useEffect.

İle useRef, örneğin, kesilebilir değişken _isMountedzaman bellek aynı referans işaret edilmektedir (bir yerel değişken)

Değişken değişken gerekiyorsa useRef , takma kancasıdır . Yerel değişkenlerin aksine, React her oluşturma sırasında aynı başvurunun döndürüldüğünden emin olur. İsterseniz , Sınıf Bileşeni'ndeki this.myVar ile aynı

Misal :

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

Aynı durumda, burada kullanılan Reaksiyon Kancaları hakkında daha fazla bilgi vereyim. Ayrıca, Fonksiyonel Bileşendeki React Hooks'u (React> 16.8 sürümü) Sınıf Bileşenindeki LifeCycle ile karşılaştıracağım.

useEffect : Çoğu yan etki kancanın içinde olur. Yan etkilere örnek olarak: veri getirme, bir abonelik ayarlama ve React bileşenlerindeki DOM'yi manuel olarak değiştirme. UseEffect, Sınıf Bileşeni'ndeki birçok LifeCycles'in yerini alır (componentDidMount, componentDidUpate, componentWillUnmount)

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

1) Varsayılan useEffect davranışı, bağımlılığınız yoksa hem ilk oluşturmadan sonra (ComponentDidMount gibi) hem de her güncelleme işleminden sonra (ComponentDidUpdate gibi) çalışır . O gibi :useEffect(fnc);

2) UseEffect için bir dizi bağımlılık verilmesi yaşam döngüsünü değiştirir. Bu örnekte: useEffect ilk oluşturma işleminden sonra bir kez çağrılır ve her sayım değiştiğinde

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

   useEffect(fnc, [count]);
}

3) bağımlılık için boş bir dizi koyarsanız, useEffect ilk oluşturma işleminden sonra yalnızca bir kez çalışır (ComponentDidMount gibi) . O gibi :useEffect(fnc, []);

4) Kaynak sızıntılarını önlemek için, kancanın ömrü sona erdiğinde (ComponentWillUnmount gibi) her şey atılmalıdır . Örneğin, boş bağımlılık dizisiyle, bileşen işlevi ayrıldıktan sonra döndürülen işlev çağrılır. O gibi :

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

useRef : döner kesilebilir bir ref nesnesi olan .Current özelliği geçirilen bağımsız değişkenin (Başlangıçdeğer) için başlatılır. Döndürülen nesne, bileşenin tüm ömrü boyunca devam eder.

Örnek: yukarıdaki soru ile burada yerel bir değişkeni kullanamayız çünkü her güncelleme işleminde kaybolacak ve yeniden başlatılacaktır.

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

  // ...
}

Böylece, useRef ve useEffect kombinasyonu ile bellek sızıntılarını tamamen temizleyebiliriz .


React Hooks hakkında daha fazla bilgi alabileceğiniz iyi bağlantılar şunlardır:

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

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


1
Bu işe yaradı. Daha sonra bugün, bunun sorunu nasıl çözdüğünü öğrenmek için sağlanan bağlantıyı okuyacağım. Cevabı detaylara dahil etmek için ayrıntılı olsaydınız harika olurdu, bu yüzden başkalarına yardımcı olacak ve ayrıca ödemesiz dönemden sonra size ödül vereceksiniz. Teşekkür ederim.
Kenny Horna

Cevabımı kabul ettiğin için teşekkür ederim. Talebinizi düşünüp yarın yapacağım.
SanjiMika

0

Sen 'cancelActiveVisits' yöntemi kullanabilirsiniz Inertiaaktif iptal etmek visitde useEffecttemizleme kancası.

Dolayısıyla bu çağrı ile aktif visitiptal edilir ve durum güncellenmez.

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

eğer Inertiaistek iptal olsun boş yanıtını işlemek için ekstra bir kontrol eklemek zorunda o zaman boş bir yanıt döndürür. Potansiyel hataları işlemek için add catch bloğu ekleyin.

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

Alternatif yol (geçici çözüm)

useRefBileşenin durumunu tutmak için kullanabilirsiniz ve buna dayalı olarak güncelleyebilirsiniz state.

Sorun:

Savaşan handleSubmitbileşen, bileşen dom'tan sökülmemiş olsa bile, bileşenin durumunu güncellemeye çalıştığı için gösteriyor .

Çözüm:

Durumunu tutmak için bir bayrak ayarlayın componenteğer componentolduğunu mountedo zaman flagdeğer olacaktır trueve eğer componentolduğunu unmountedbayrak değeri yanlış olacaktır. Buna dayanarak güncelleyebiliriz state. Bayrak durumu useRefiçin referans tutmak için kullanabiliriz .

useRef.currentözelliği geçirilen bağımsız değişkene (initialValue) başlatılan değişken bir ref nesnesi döndürür . Döndürülen nesne, bileşenin tüm ömrü boyunca devam eder. Buna useEffectkarşılık, eğer sökülmüşse, bileşenin durumunu ayarlayacak bir fonksiyon.

Ve sonra useEffecttemizleme fonksiyonunda bayrağıfalse.

useEffecr temizleme işlevi

useEffectKancanın bir temizleme işlevini kullanarak verir. Efekt artık geçerli olmadığında, örneğin bu efekti kullanan bir bileşen kaldırıldığında, bu işlev her şeyi temizlemek için çağrılır. Bizim durumumuzda, bayrağı yanlış olarak ayarlayabiliriz.

Misal:

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

HandleSubmit'te bileşenin takılı olup olmadığını kontrol edebilir ve durumu buna göre güncelleyebiliriz.

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

Aksi takdirde _componentStatus, bellek sızıntılarını önlemek için null değerini ayarlayın .


İşe yaramadı: /
Kenny Horna

Eğer değerini dışarı teselli Could ajaxCalluseEffect. ve değerin ne olduğunu görün
Sohail

Gecikme için üzgünüm. Geri döner undefined. Ben hemen sonra ekledimreturn () => {
Kenny Horna

Kodu değiştirdim, lütfen yeni kodu deneyin.
Sohail

Bunun bir düzeltme veya bu sorunu çözmenin doğru yolu olduğunu söylemeyeceğim, ancak bu uyarıyı kaldıracaktır.
Sohail
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.