क्लीनअप मेमोरी, रिएक्ट हुक में एक अनमाउंटेड घटक पर लीक होती है


19

मैं रिएक्ट का उपयोग करके नया हूं, इसलिए यह वास्तव में सरल हो सकता है लेकिन मैं इसे अपने आप से बाहर नहीं निकाल सकता, भले ही मैंने कुछ शोध किया हो। मुझे माफ कर दो अगर यह बहुत गूंगा है।

प्रसंग

मैं लारवेल (बैकएंड) और रिएक्ट (फ्रंट-एंड) एडेप्टर के साथ Inertia.js का उपयोग कर रहा हूं । यदि आप जड़ता को नहीं जानते हैं, तो यह मूल रूप से है:

Inertia.js आपको क्लासिक सर्वर-साइड रूटिंग और कंट्रोलर्स का उपयोग करके आधुनिक सिंगल-पेज रिएक्ट, Vue और Svelte ऐप्स को जल्दी से बनाने देता है।

मुद्दा

मैं एक साधारण लॉगिन पेज बना रहा हूं जिसमें एक फॉर्म है जिसे सबमिट करने पर अगले पृष्ठ को लोड करने के लिए एक POST अनुरोध किया जाएगा। यह ठीक काम करने लगता है लेकिन अन्य पृष्ठों में कंसोल निम्नलिखित चेतावनी दिखाता है:

चेतावनी: अनमाउंट किए गए घटक पर प्रतिक्रिया स्थिति अद्यतन नहीं कर सकता। यह एक नो-ऑप है, लेकिन यह आपके एप्लिकेशन में मेमोरी लीक होने का संकेत देता है। उपयोग करने के लिए, क्लीनअप फ़ंक्शन में सभी सदस्यताएँ और अतुल्यकालिक कार्यों को रद्द करें।

लॉगिन में (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;

@ शोहेल ने घटक का पूरा कोड
केनी होर्ना

आप बस को दूर करने की कोशिश की .then(() => {})?
ग्वारिक पी

जवाबों:


22

क्योंकि यह async वादा कॉल है, इसलिए आपको async प्रतिक्रिया के अगले उपचार के लिए पहले से अनमाउंट किए गए घटक (मेमोरी लीक से बचते हुए) की जाँच करने के लिए एक उत्परिवर्तित रेफ चर (useRef के साथ) का उपयोग करना होगा :

चेतावनी: अनमाउंट किए गए घटक पर प्रतिक्रिया स्थिति अद्यतन नहीं कर सकता।

दो रिएक्ट हुक जो आपको इस मामले में उपयोग करने चाहिए: useRefऔर useEffect

साथ useRef, उदाहरण के लिए, अस्थायी चर _isMountedहमेशा स्मृति में एक ही संदर्भ में बताया है (नहीं एक स्थानीय चर)

उपयोग योग्य है अगर परिवर्तनशील चर की जरूरत है तो गो-टू हुक है। स्थानीय चर के विपरीत, रिएक्ट सुनिश्चित करता है कि प्रत्येक रेंडर के दौरान एक ही संदर्भ दिया जाता है। यदि आप चाहते हैं, तो क्लास कंपोनेंट में इस .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... ;
            }
         )
  }
}

उसी अवसर पर, मैं आपको यहाँ प्रयुक्त रिएक्ट हुक के बारे में अधिक जानकारी के बारे में बताता हूँ। इसके अलावा, मैं क्लास कंपोनेंट में लाइफ़ साइकल के साथ फंक्शनल हुक (संस्करण रिएक्ट> 16.8) में रिएक्ट हुक की तुलना करूँगा।

उपयोग : अधिकांश दुष्प्रभाव हुक के अंदर होते हैं। साइड इफेक्ट्स के उदाहरण हैं: डेटा प्राप्त करना, सदस्यता स्थापित करना, और मैन्युअल रूप से DOM को React घटकों में बदलना। UseEffect क्लास कम्पोनेंट में बहुत सारे जीवन चक्रों की जगह ले लेता है (ComponentsDidMount, ComponentsDidUpate, ComponentsWillUnmount)

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

1) उपयोग का डिफ़ॉल्ट व्यवहार पहले रेंडर (जैसे ComponentDidMount) और हर अपडेट रेंडर (जैसे ComponentDidUpdate) के बाद चलता है, अगर आपके पास निर्भरता नहीं है। यह उस तरह से :useEffect(fnc);

2) उपयोग करने के लिए निर्भरता की सरणी देने से इसका जीवनचक्र बदल जाएगा। इस उदाहरण में: useEffect को पहले रेंडर के बाद एक बार बुलाया जाएगा और हर बार काउंट में बदलाव होगा

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

   useEffect(fnc, [count]);
}

3) उपयोग पूर्ण होने के बाद केवल एक बार ही चलेंगे (जैसे कि ComponentDidMount) यदि आप निर्भरता के लिए खाली सरणी रखते हैं। यह उस तरह से :useEffect(fnc, []);

4) संसाधन लीक को रोकने के लिए, हुक के जीवनचक्र समाप्त होने पर सब कुछ निपटाया जाना चाहिए (जैसे ComponentWillUnmount) । उदाहरण के लिए, निर्भरता के खाली सरणी के साथ, घटक अनमाउंट के बाद लौटाया गया फ़ंक्शन कहा जाएगा। यह उस तरह से :

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

useRef : एक उत्परिवर्तित रेफ ऑब्जेक्ट लौटाता है। जिसका समांतर गुण पारित तर्क (initialValue) से आरंभ होता है। लौटाई गई वस्तु घटक के पूर्ण जीवनकाल के लिए बनी रहेगी।

उदाहरण: ऊपर दिए गए प्रश्न के साथ, हम यहां एक स्थानीय चर का उपयोग नहीं कर सकते हैं क्योंकि यह खो जाएगा और प्रत्येक अपडेट रेंडर पर फिर से शुरू किया जाएगा।

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 के संयोजन से , हम मेमोरी लीक्स को पूरी तरह से साफ कर सकते हैं।


रिएक्ट हुक के बारे में आप जो अच्छे लिंक पढ़ सकते हैं वे हैं:

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

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


1
यह काम किया। बाद में आज मैं वास्तव में प्राप्त करने के लिए दिए गए लिंक को पढ़ूंगा कि यह समस्या कैसे हल करता है। यदि आप विवरण को शामिल करने के लिए प्रतिक्रिया पर विस्तार से बता सकते हैं तो यह बहुत अच्छा होगा इसलिए यह दूसरों के लिए उपयोगी होगा और अनुग्रह अवधि के बाद आपको इनाम देने के लिए भी। धन्यवाद।
केनी होर्ना

मेरे उत्तर को स्वीकार करने के लिए आपका धन्यवाद। मैं आपके अनुरोध के बारे में सोचूंगा और कल करूंगा।
सनजीमिका

0

आप क्लीनअप हुक में Inertiaसक्रिय को रद्द करने के लिए 'CancelActiveVisits' विधि का उपयोग कर सकते हैं ।visituseEffect

तो इस कॉल के साथ सक्रिय visitको रद्द कर दिया जाएगा और राज्य अपडेट नहीं होगा।

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

यदि Inertiaअनुरोध रद्द हो जाता है तो यह एक खाली प्रतिक्रिया लौटाएगा, इसलिए आपको खाली प्रतिक्रिया को संभालने के लिए एक अतिरिक्त चेक जोड़ना होगा। किसी भी संभावित त्रुटियों को संभालने के लिए कैच ब्लॉक जोड़ें।

 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घटक की स्थिति को अपडेट करने की कोशिश कर रहा है, भले ही घटक ने अनमाउंट फॉर्म को डोम बनाया हो।

समाधान:

की स्थिति धारण करने के लिए एक ध्वज सेट करें component, यदि componentहै mountedतो flagमान होगा trueऔर यदि componentहै unmountedझंडा मूल्य झूठी हो जाएगा। तो इसके आधार पर हम अपडेट कर सकते हैं state। ध्वज की स्थिति के लिए हम useRefएक संदर्भ रखने के लिए उपयोग कर सकते हैं ।

useRefएक उत्परिवर्तित रेफ ऑब्जेक्ट लौटाता है, जिसकी .currentसंपत्ति उत्तीर्ण तर्क (initialValue) से शुरू होती है। लौटाई गई वस्तु घटक के पूर्ण जीवनकाल के लिए बनी रहेगी। में useEffectएक समारोह है, जो घटक की स्थिति सेट हो जाएगा अगर अनमाउंट है लौटने।

और फिर useEffectसफाई फ़ंक्शन में हम ध्वज को सेट कर सकते हैंfalse.

useEffecr क्लीनअप फंक्शन

useEffectहुक एक सफाई समारोह का उपयोग कर सकते हैं। कभी भी प्रभाव अब मान्य नहीं है, उदाहरण के लिए जब उस प्रभाव का उपयोग करने वाला एक घटक अनमाउंटिंग है, तो यह फ़ंक्शन सब कुछ साफ करने के लिए कहा जाता है। हमारे मामले में, हम ध्वज को असत्य पर सेट कर सकते हैं।

उदाहरण:

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मेमोरी लीक से बचने के लिए नल को सेट करें ।


यह काम नहीं किया: /
केनी हॉर्ना

क्या आप ajaxCallअंदर के मूल्य को सांत्वना दे सकते हैं useEffect। और देखें कि मूल्य क्या है
सोहेल

विलंब के लिए क्षमा चाहते हैं। यह लौट आता है undefined। मैं बस के बाद में जोड़ दिया हैreturn () => {
केनी हॉर्ना

मैंने कोड बदल दिया है, कृपया नया कोड आज़माएँ।
सोहेल

मैं यह नहीं कहूंगा कि यह इस मुद्दे को हल करने का एक सही या सही तरीका है, लेकिन यह चेतावनी को हटा देगा।
सोहेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.