[javascript] 반응 후크의 마운트되지 않은 구성 요소에서 정리 메모리 누수

나는 React를 처음 사용하기 때문에 달성하기가 정말 간단 할 수는 있지만 조사를 해 보았지만 스스로 알아낼 수는 없습니다. 이것이 너무 멍청하면 용서하십시오.

문맥

Laravel (백엔드) 및 React (프런트 엔드) 어댑터와 함께 Inertia.js 를 사용 하고 있습니다. 관성을 모르는 경우 기본적으로 다음과 같습니다.

Inertia.js를 사용하면 클래식 서버 측 라우팅 및 컨트롤러를 사용하여 최신 단일 페이지 React, Vue 및 Svelte 앱을 빠르게 구축 할 수 있습니다.

발행물

제출할 때 다음 페이지를로드하기 위해 POST 요청을 수행하는 양식의 간단한 로그인 페이지를 만들고 있습니다. 잘 작동하는 것 같지만 다른 페이지에서는 콘솔에 다음 경고가 표시됩니다.

경고 : 마운트 해제 된 구성 요소에서 반응 상태 업데이트를 수행 할 수 없습니다. 이것은 작동하지 않지만 응용 프로그램의 메모리 누수를 나타냅니다. 수정하려면 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;



답변

비동기 약속 호출이기 때문에 다음과 같은 비동기 응답 처리 (메모리 누수 방지 ) 를 위해 이미 마운트 해제 된 구성 요소를 확인하려면 mutable ref 변수 (useRef 포함) 를 사용해야 합니다 .

경고 : 마운트 해제 된 구성 요소에서 반응 상태 업데이트를 수행 할 수 없습니다.

이 경우 사용해야하는 두 개의 반응 고리 : useRefuseEffect.

부착은 useRef, 예를 들면, 가변 변수는 _isMounted항상 메모리에있는 동일한 참조에 놓고 (하지 로컬 변수)

가변 변수가 필요한 경우 useRef 는 이동 후크입니다. 로컬 변수와 달리 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... ;
            }
         )
  }
}

같은 경우 여기에 사용 된 반응 고리에 대한 자세한 정보를 설명하겠습니다. 또한 Functional Component (React> 16.8 버전)의 React Hooks와 Class Component의 LifeCycle을 비교합니다.

useEffect : 대부분의 부작용은 후크 내부에서 발생합니다. 부작용의 예로는 데이터 가져 오기, 구독 설정 및 React 컴포넌트에서 DOM 수동 변경이 있습니다. useEffect는 클래스 컴포넌트 (componentDidMount, componentDidUpate, componentWillUnmount)의 많은 LifeCycles를 대체합니다.

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

1) 기본 동작 useEffect 는 종속성이없는 경우 첫 번째 렌더링 (예 : ComponentDidMount) 후 및 모든 업데이트 렌더링 (예 : ComponentDidUpdate) 후에 실행됩니다 . 그것은 같습니다 :useEffect(fnc);

2) 사용할 종속성 배열을 제공하면 수명주기가 변경됩니다. 이 예제에서 : 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 : 변경 가능한 참조 객체 를 반환 합니다 . .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
    }
  }, []);

  // ...
}

따라서 useRefuseEffect의 조합으로 메모리 누수를 완전히 정리할 수 있습니다.


React Hooks에 대한 자세한 내용은 다음 링크를 참조하십시오.

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

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


답변

당신의 ‘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플래그 값이 false가 될 것입니다. 이를 바탕으로을 업데이트 할 수 있습니다 state. 플래그 상태의 useRef경우 참조를 보유하는 데 사용할 수 있습니다 .

useRef.current전달 된 인수 (initialValue) 로 속성이 초기화 된 변경 가능한 참조 객체를 반환합니다 . 반환 된 객체는 구성 요소의 전체 수명 동안 지속됩니다. 에서는 useEffect마운트 해제되면, 구성 요소의 상태를 설정하는 기능을 리턴한다.

그리고 useEffect클린업 기능에서 플래그를false.

Effecr 정리 기능 사용

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로 설정하십시오 .


답변