Dọn dẹp bộ nhớ bị rò rỉ trên một Thành phần chưa từng có trong React Hook


19

Tôi mới sử dụng React, vì vậy điều này có thể thực sự đơn giản để đạt được nhưng tôi không thể tự mình tìm ra nó mặc dù tôi đã thực hiện một số nghiên cứu. Hãy tha thứ cho tôi nếu điều này quá ngu ngốc.

Bối cảnh

Tôi đang sử dụng Inertia.js với bộ điều hợp Laravel (phụ trợ) và React (front-end). Nếu bạn không biết quán tính, về cơ bản:

Inertia.js cho phép bạn nhanh chóng xây dựng các ứng dụng React, Vue và Svelte một trang hiện đại bằng cách sử dụng bộ điều khiển và định tuyến phía máy chủ cổ điển.

Vấn đề

Tôi đang làm một trang đăng nhập đơn giản có một biểu mẫu mà khi gửi sẽ thực hiện yêu cầu POST để tải trang tiếp theo. Nó có vẻ hoạt động tốt nhưng trong các trang khác, bảng điều khiển hiển thị cảnh báo sau:

Cảnh báo: Không thể thực hiện cập nhật trạng thái Phản ứng trên một thành phần chưa từng có. Đây là một no-op, nhưng nó chỉ ra rò rỉ bộ nhớ trong ứng dụng của bạn. Để khắc phục, hủy tất cả các đăng ký và tác vụ không đồng bộ trong chức năng dọn dẹp useEffect.

trong đăng nhập (được tạo bởi quán tính)

Mã liên quan (Tôi đã đơn giản hóa nó để tránh các dòng không liên quan):

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;

Bây giờ, tôi biết rằng tôi phải thực hiện chức năng dọn dẹp vì lời hứa của yêu cầu là thứ tạo ra cảnh báo này. Tôi biết rằng tôi nên sử dụng useEffectnhưng tôi không biết làm thế nào để áp dụng nó trong trường hợp này. Tôi đã thấy ví dụ khi một giá trị thay đổi, nhưng làm thế nào để thực hiện nó trong một cuộc gọi loại này?

Cảm ơn trước.


Cập nhật

Theo yêu cầu, mã đầy đủ của thành phần này:

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 Tôi đã thêm mã đầy đủ của thành phần
Kenny Horna

Bạn đã cố gắng để loại bỏ đơn giản .then(() => {})?
Guerric P

Câu trả lời:


22

Vì đó là cuộc gọi hứa hẹn không đồng bộ, do đó bạn phải sử dụng biến ref có thể thay đổi (với useRef) để kiểm tra thành phần chưa được kết nối để xử lý tiếp theo cho phản hồi không đồng bộ (tránh rò rỉ bộ nhớ):

Cảnh báo: Không thể thực hiện cập nhật trạng thái Phản ứng trên một thành phần chưa từng có.

Hai móc phản ứng mà bạn nên sử dụng trong trường hợp này: useRefuseEffect.

Với useRef, ví dụ, các biến có thể thay đổi _isMountedluôn luôn chỉ vào tham khảo cùng trong bộ nhớ (không phải là một biến cục bộ)

useRef là hook-to hook nếu cần biến có thể thay đổi. Không giống như các biến cục bộ, React đảm bảo cùng một tham chiếu được trả về trong mỗi lần kết xuất. Nếu bạn muốn, nó giống với this.myVar trong Thành phần lớp

Thí dụ :

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

Nhân dịp này, hãy để tôi giải thích cho bạn thêm thông tin về React Hook được sử dụng ở đây. Ngoài ra, tôi sẽ so sánh móc React trong Thành phần chức năng (phiên bản React> 16.8) với LifeCycle trong Thành phần lớp.

useEffect : Hầu hết các tác dụng phụ xảy ra bên trong hook. Ví dụ về các tác dụng phụ là: tìm nạp dữ liệu, thiết lập đăng ký và thay đổi thủ công DOM trong các thành phần React. UseEffect thay thế rất nhiều Vòng đời trong Thành phần lớp (thành phầnDidMount, thành phầnDidUpate, thành phầnWillUnmount)

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

1) Hành vi mặc định của useEffect chạy cả sau lần kết xuất đầu tiên (như ElementDidMount ) và sau mỗi lần cập nhật kết xuất (như ElementDidUpdate ) nếu bạn không có phụ thuộc. Nó giống như thế:useEffect(fnc);

2) Đưa ra các mảng phụ thuộc để sử dụngEffect sẽ thay đổi vòng đời của nó. Trong ví dụ này: useEffect sẽ được gọi một lần sau lần kết xuất đầu tiên và mỗi lần thay đổi số lần đếm

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

   useEffect(fnc, [count]);
}

3) useEffect sẽ chỉ chạy một lần sau lần kết xuất đầu tiên (như ElementDidMount ) nếu bạn đặt một mảng trống cho sự phụ thuộc. Nó giống như thế:useEffect(fnc, []);

4) Để tránh rò rỉ tài nguyên, mọi thứ phải được xử lý khi vòng đời của móc kết thúc (như ElementWillUnmount) . Ví dụ, với mảng phụ thuộc trống, hàm trả về sẽ được gọi sau khi ngắt kết nối thành phần. Nó giống như thế:

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

useRef : trả về một đối tượng ref có thể thay đổithuộc tính .c hiện được khởi tạo cho đối số được truyền (initValue). Đối tượng trả về sẽ tồn tại trong suốt vòng đời của thành phần.

Ví dụ: với câu hỏi ở trên, chúng tôi không thể sử dụng biến cục bộ ở đây vì nó sẽ bị mất và được bắt đầu lại trên mỗi kết xuất cập nhật.

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

  // ...
}

Vì vậy, với sự kết hợp của useRefuseEffect , chúng tôi hoàn toàn có thể dọn sạch các rò rỉ bộ nhớ.


Các liên kết tốt mà bạn có thể đọc thêm về React Hook là:

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

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


1
Điều này đã làm việc. Sau hôm nay tôi sẽ đọc liên kết được cung cấp để thực sự hiểu cách giải quyết vấn đề này. Nếu bạn có thể giải thích về phản hồi để bao gồm các chi tiết sẽ rất tuyệt vì vậy nó sẽ hữu ích cho người khác và cũng để trao tiền thưởng cho bạn sau thời gian ân hạn. Cảm ơn bạn.
Kenny Horna

Cảm ơn bạn đã chấp nhận câu trả lời của tôi. Tôi sẽ suy nghĩ về yêu cầu của bạn và thực hiện nó vào ngày mai.
SanjiMika

0

Bạn có thể sử dụng phương thức 'CancActiveVisits' Inertiađể hủy kích hoạt visittrong useEffectmóc dọn dẹp.

Vì vậy, với cuộc gọi này, hoạt động visitsẽ bị hủy và trạng thái sẽ không được cập nhật.

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

nếu Inertiayêu cầu bị hủy thì nó sẽ trả về một phản hồi trống, do đó bạn phải thêm một kiểm tra bổ sung để xử lý phản hồi trống. Thêm thêm khối bắt cũng để xử lý bất kỳ lỗi tiềm ẩn.

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

Cách khác (cách giải quyết)

Bạn có thể sử dụng useRefđể giữ trạng thái của thành phần và dựa trên điều này, bạn có thể cập nhật state.

Vấn đề:

Các cảnh báo đang hiển thị bởi vì handleSubmitđang cố gắng cập nhật trạng thái của thành phần mặc dù thành phần này chưa từng có dạng dom.

Giải pháp:

Đặt cờ để giữ trạng thái của component, nếu componentmountedthì flaggiá trị sẽ là truevà nếu componentunmountedgiá trị cờ sẽ là sai. Vì vậy, dựa trên điều này, chúng tôi có thể cập nhật state. Đối với trạng thái cờ chúng ta có thể sử dụng useRefđể giữ một tham chiếu.

useReftrả về một đối tượng ref có thể thay đổi có thuộc .currenttính được khởi tạo cho đối số được truyền (initValue). Đối tượng trả về sẽ tồn tại trong suốt vòng đời của thành phần. Trong useEffecttrả về một chức năng mà sẽ thiết lập trạng thái của các thành phần, nếu nó được gỡ bỏ.

Và sau đó trong useEffectchức năng dọn dẹp, chúng ta có thể đặt cờ thànhfalse.

sử dụng chức năng dọn dẹpEffecr

Các useEffectmóc cho phép sử dụng một chức năng dọn dẹp. Bất cứ khi nào hiệu ứng không còn hiệu lực, ví dụ khi một thành phần sử dụng hiệu ứng đó không được kết nối, chức năng này được gọi để dọn sạch mọi thứ. Trong trường hợp của chúng tôi, chúng tôi có thể đặt cờ thành false.

Thí dụ:

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

Và trong handleSubmit, chúng ta có thể kiểm tra xem thành phần có được gắn kết hay không và cập nhật trạng thái dựa trên điều này.

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

Mặt khác, đặt thành _componentStatusnull để tránh rò rỉ bộ nhớ.


Nó không hoạt động: /
Kenny Horna

Bạn có thể điều khiển giá trị ajaxCallbên trong useEffect. và xem giá trị là gì
Sohail

Xin lỗi về sự chậm trễ. Nó trở lại undefined. Tôi đã thêm nó ngay saureturn () => {
Kenny Horna

Tôi đã thay đổi mã, Vui lòng thử mã mới.
Sohail

Tôi sẽ không nói rằng đây là một sửa chữa hoặc cách chính xác để giải quyết vấn đề này, nhưng điều này sẽ loại bỏ cảnh báo.
Sohail
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.