使用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 的回调用于后续调用的清理。
我想了解为什么没有 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 的回调用于后续调用的清理。