使用Effect清理功能?内存泄漏错误

useEffect cleanup function? Memory leak error

我想了解为什么没有 AbortionController.abort 函数的 useEffect 挂钩无法正常工作。

我在 app.js 中有一个嵌套路由,例如:

<Route path='/profile' element={<PrivateRoute />}>
   <Route path='/profile' element={<Profile />} />
</Route>

比二分量: 私人路线:

import { Navigate, Outlet } from 'react-router-dom';
import { useAuthStatus } from '../hooks/useAuthStatus';

export default function PrivateRoute() {
  const { loggedIn, checkingStatus } = useAuthStatus();

  if (checkingStatus) return <h1>Loading...</h1>;
  return loggedIn ? <Outlet /> : <Navigate to='/sign-in' />;
}

简介:

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { getAuth, updateProfile } from 'firebase/auth';
import { updateDoc, doc } from 'firebase/firestore';
import { db } from '../firebase.config';
import { toast } from 'react-toastify';

export default function Profile() {
  const auth = getAuth();
  const [changeDetails, setChangeDetails] = useState(false);
  const [formData, setFormData] = useState({
    name: auth.currentUser.displayName,
    email: auth.currentUser.email,
  });

  const { name, email } = formData;

  const navigate = useNavigate();

  const onLogout = () => {
    auth.signOut();
    navigate('/');
  };

  const onSubmit = async (e) => {
    try {
      if (auth.currentUser.displayName !== name) {
        //update display name if fb
        await updateProfile(auth.currentUser, {
          displayName: name,
        });
        //update in firestore
        const userRef = doc(db, 'users', auth.currentUser.uid);
        await updateDoc(userRef, {
          name,
        });
      }
    } catch (error) {
      toast.error('Could not update profile details');
    }
  };

  const onChange = (e) => {
    setFormData((prev) => ({
      ...prev,
      [e.target.id]: e.target.value,
    }));
  };
  return (
    <div className='profile'>
      <header className='profileHeader'>
        <p className='pageHeader'>My Profile</p>
        <button type='button' className='logOut' onClick={onLogout}>
          Logout
        </button>
      </header>
      <main>
        <div className='profileDetailsHeader'>
          <p className='profileDetailsText'>Personal Details</p>
          <p
            className='changePersonalDetails'
            onClick={() => {
              setChangeDetails((prev) => !prev);
              changeDetails && onSubmit();
            }}
          >
            {changeDetails ? 'done' : 'change'}
          </p>
        </div>
        <div className='profileCard'>
          <form>
            <input
              type='text'
              id='name'
              className={!changeDetails ? 'profileName' : 'profileNameActive'}
              disabled={!changeDetails}
              value={name}
              onChange={onChange}
            />
            <input
              type='text'
              id='email'
              className={!changeDetails ? 'profileEmail' : 'profileEmailActive'}
              disabled={!changeDetails}
              value={email}
              onChange={onChange}
            />
          </form>
        </div>
      </main>
    </div>
  );
}

和自定义 Hook:

import { useEffect, useState } from 'react';
import { getAuth, onAuthStateChanged } from 'firebase/auth';

export function useAuthStatus() {
  const [loggedIn, setLoggedIn] = useState(false);
  const [checkingStatus, setCheckingStatus] = useState(true);

  useEffect(() => {
    const abortCont = new AbortController();
    const auth = getAuth();
    onAuthStateChanged(auth, (user) => {
      if (user) {
        setLoggedIn(true);
      } else {
        setLoggedIn(false);
      }
      setCheckingStatus(false);
    });
    //  return () => abortCont.abort();
  }, [setLoggedIn, setCheckingStatus]);
  return { loggedIn, checkingStatus };
}

你能解释一下为什么我会收到错误消息:警告:无法对未安装的组件执行 React 状态更新。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要修复,请在 useEffect 清理函数中取消所有订阅和异步任务。

我找到了 AbortController 的解决方案,但仍然不明白问题是什么。

这个错误是随机出现的,有时在我登录时出现,有时在我未登录时出现,然后尝试进入个人资料页面。该应用程序运行良好,只是想了解幕后情况。

如果我明白了,如果我没有登录,那么它会渲染'Sign-in'页面,如果我登录了,那么就会渲染'Profile'页面,也有是一个加载页面,但事实并非如此。所以,很简单,如果我登录了就渲染这个页面,如果没有登录就渲染另一个页面。那么问题在哪里?为什么我需要 AbortController 函数?

onAuthStateChanges 将永远聆听您的 useEffect 钩子。每次挂钩 运行 时都需要取消订阅,否则会出现这些内存泄漏。在您的情况下,即使组件已卸载,用户身份验证状态的更改也会尝试调用 setLoggedIn

查看 onAuthStateChanged (https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#onauthstatechanged) 的文档,它 return 是 firebase.Unsubscribe

你必须这样做:

useEffect(() => {
  const auth = getAuth();
  const unsubscribe = onAuthStateChanged(auth, (user) => {
    if (user) {
      setLoggedIn(true);
    } else {
      setLoggedIn(false);
    }
    setCheckingStatus(false);
  });

  return () => unsubscribe();
})

您可以在 useEffect 挂钩中选择 return 的回调用于后续调用的清理。