หน่วยความจำการล้างข้อมูลรั่วไหลบนคอมโพเนนต์ที่ไม่ได้ประกอบเข้าไปใน React Hooks


19

ฉันใหม่โดยใช้ React ดังนั้นนี่อาจเป็นเรื่องง่ายที่จะประสบความสำเร็จ แต่ฉันไม่สามารถหาคำตอบได้ด้วยตัวเองแม้ว่าฉันจะทำวิจัยแล้วก็ตาม ยกโทษให้ฉันถ้านี่มันโง่เกินไป

บริบท

ฉันใช้Inertia.jsกับอะแดปเตอร์ Laravel (แบ็กเอนด์) และ React (front-end) หากคุณไม่รู้จักความเฉื่อยมันเป็นเรื่องปกติ:

Inertia.js ให้คุณสร้างแอป React, Vue และ Svelte แบบหน้าเดียวที่ทันสมัยได้อย่างรวดเร็วโดยใช้การกำหนดเส้นทางและตัวควบคุมฝั่งเซิร์ฟเวอร์แบบคลาสสิก

ปัญหา

ฉันกำลังทำหน้าเข้าสู่ระบบแบบง่ายที่มีแบบฟอร์มที่เมื่อส่งจะทำการร้องขอ POST เพื่อโหลดหน้าถัดไป ดูเหมือนว่าจะทำงานได้ดี แต่ในหน้าอื่น ๆ คอนโซลจะแสดงคำเตือนต่อไปนี้:

คำเตือน: ไม่สามารถทำการอัพเดทสถานะทำปฏิกิริยากับส่วนประกอบที่ไม่ได้ประกอบเข้าไป นี่คือไม่ใช้งาน แต่ระบุว่ามีหน่วยความจำรั่วในแอปพลิเคชันของคุณ ในการแก้ไขให้ยกเลิกการสมัครสมาชิกและงานอะซิงโครนัสทั้งหมดในฟังก์ชัน useEffect cleanup

ในการเข้าสู่ระบบ (สร้างโดย 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;

@Sail ฉันได้เพิ่มรหัสเต็มขององค์ประกอบ
Kenny Horna

คุณลองเพียงแค่ลบ.then(() => {})?
Guerric P

คำตอบ:


22

เนื่องจากเป็นการเรียกสัญญาแบบ async ดังนั้นคุณต้องใช้ตัวแปร ref ที่ไม่แน่นอน (ด้วย useRef)เพื่อตรวจสอบส่วนประกอบที่ไม่ได้ประกอบเข้ากับการปฏิบัติต่อการตอบสนองแบบ async ครั้งถัดไป (หลีกเลี่ยงการรั่วไหลของหน่วยความจำ):

คำเตือน: ไม่สามารถทำการอัพเดทสถานะทำปฏิกิริยากับส่วนประกอบที่ไม่ได้ประกอบเข้าไป

สองตอบสนองเบ็ดที่คุณควรใช้ในกรณีนี้: และuseRefuseEffect

ด้วยuseRefตัวอย่างเช่นตัวแปรที่ไม่แน่นอน_isMountedชี้เสมอที่อ้างอิงเดียวกันในหน่วยความจำ(ไม่ได้เป็นตัวแปรท้องถิ่น)

useRefเป็นตะขอ go-to หากต้องการตัวแปรที่ไม่แน่นอน ไม่เหมือนกับตัวแปรโลคัล React ทำให้แน่ใจว่าการอ้างอิงเดียวกันถูกส่งคืนระหว่างการแสดงผลแต่ละครั้ง ถ้าคุณต้องการมันก็เหมือนกันกับthis.myVar ใน 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 Hook ใน Function Component (เวอร์ชัน React> 16.8) กับ LifeCycle ใน Class Component

useEffect : ผลข้างเคียงส่วนใหญ่เกิดขึ้นภายใน hook ตัวอย่างของผลข้างเคียงคือ: การดึงข้อมูล, การตั้งค่าการสมัครสมาชิก, และการเปลี่ยน DOM ในคอมโพเนนต์ React ด้วยตนเอง useEffect แทนที่ LifeCycles จำนวนมากใน Class Component (componentDidMount, componentDidUpate, componentWillUnmount)

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

1) พฤติกรรมเริ่มต้นของ useEffect รันทั้งสองหลังจากการเรนเดอร์ครั้งแรก(เช่น ComponentDidMount)และหลังจากการเรนเดอร์ทุกครั้ง(เช่น ComponentDidUpdate)หากคุณไม่มีการพึ่งพา ประมาณนั้นแหละ :useEffect(fnc);

2) การให้อาร์เรย์ของการพึ่งพาเพื่อ useEffect จะเปลี่ยนวงจรชีวิตของมัน ในตัวอย่างนี้: useEffect จะถูกเรียกหนึ่งครั้งหลังจากการเรนเดอร์แรกและทุกครั้งที่มีการเปลี่ยนแปลง

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

   useEffect(fnc, [count]);
}

3) useEffect จะทำงานเพียงครั้งเดียวหลังจากการเรนเดอร์ครั้งแรก(เช่น ComponentDidMount)หากคุณใส่อาร์เรย์ว่างสำหรับการพึ่งพา ประมาณนั้นแหละ :useEffect(fnc, []);

4) เพื่อป้องกันการรั่วไหลของทรัพยากรทุกอย่างต้องทิ้งเมื่อวงจรชีวิตของเบ็ดปลาย(เช่น ComponentWillUnmount) ตัวอย่างเช่นกับอาเรย์ที่ว่างเปล่าของการพึ่งพาฟังก์ชั่นกลับมาจะถูกเรียกหลังจากส่วนประกอบ unmounts ประมาณนั้นแหละ :

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

useRef : ส่งคืนออบเจ็กต์อ้างอิงที่ไม่แน่นอนซึ่งมีคุณสมบัติ . currentถูกเตรียมใช้งานกับอาร์กิวเมนต์ที่ส่งผ่าน (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เราสามารถล้างการรั่วไหลของหน่วยความจำได้อย่างสมบูรณ์


ลิงค์ที่ดีที่คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ React Hooks คือ:

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

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


1
สิ่งนี้ใช้ได้ผล ต่อมาวันนี้ฉันจะอ่านลิงก์ที่ให้ไว้เพื่อรับวิธีการแก้ปัญหา หากคุณสามารถอธิบายรายละเอียดเกี่ยวกับการรวมรายละเอียดได้อย่างยอดเยี่ยมดังนั้นมันจะเป็นประโยชน์กับผู้อื่นและจะมอบรางวัลให้คุณหลังจากช่วงเวลาผ่อนผัน ขอบคุณ.
Kenny Horna

ขอบคุณสำหรับการยอมรับคำตอบของฉัน ฉันจะคิดถึงคำขอของคุณและทำในวันพรุ่งนี้
SanjiMika

0

คุณสามารถใช้วิธีการ 'cancelActiveVisits' Inertiaเพื่อยกเลิกการใช้งานvisitในuseEffecthook การล้างข้อมูล

ดังนั้นด้วยการโทรนี้การใช้งานvisitจะถูกยกเลิกและสถานะจะไม่ได้รับการอัพเดต

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

ถ้าInertiaคำขอถูกยกเลิกก็จะส่งคืนการตอบกลับที่ว่างเปล่าดังนั้นคุณต้องเพิ่มการตรวจสอบพิเศษเพื่อจัดการการตอบกลับที่ว่างเปล่า เพิ่มเพิ่ม catch catch block เพื่อจัดการข้อผิดพลาดที่อาจเกิดขึ้น

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

ทางเลือก (วิธีแก้ปัญหา)

คุณสามารถใช้ในการถือสถานะของส่วนประกอบและอยู่บนพื้นฐานนี้คุณสามารถอัปเดตuseRefstate

ปัญหา:

การสงครามกำลังแสดงขึ้นเนื่องจากhandleSubmitกำลังพยายามอัพเดทสถานะขององค์ประกอบแม้ว่าส่วนประกอบนั้นจะไม่ได้ต่อเชื่อมกับรูปแบบของ dom

สารละลาย:

ตั้งค่าสถานะเพื่อเก็บสถานะของ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โมฆะเพื่อหลีกเลี่ยงหน่วยความจำรั่ว


มันใช้งานไม่ได้: /
Kenny Horna

คุณช่วยปลอบใจค่าของajaxCallข้างในuseEffectได้ไหม และดูว่ามูลค่าคืออะไร
Sohail

ขออภัยในความล่าช้า. มันกลับundefinedมา ฉันได้เพิ่มมันหลังจากreturn () => {
Kenny Horna

ฉันเปลี่ยนรหัสแล้วโปรดลองรหัสใหม่
Sohail

ฉันจะไม่บอกว่านี่เป็นการแก้ไขหรือวิธีที่ถูกต้องในการแก้ไขปัญหานี้ แต่สิ่งนี้จะลบการเตือน
Sohail
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.