我有这个代码
import ReactDOM from "react-dom";
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function ParamsExample() {
return (
<Router>
<div>
<h2>Accounts</h2>
<Link to="/">Netflix</Link>
<Route path="/" component={Miliko} />
</div>
</Router>
);
}
const Miliko = ({ match }) => {
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
(async function() {
setIsError(false);
setIsLoading(true);
try {
const Res = await fetch("https://foo0022.firebaseio.com/New.json");
const ResObj = await Res.json();
const ResArr = await Object.values(ResObj).flat();
setData(ResArr);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
})();
console.log(data);
}, [match]);
return <div>{`${isLoading}${isError}`}</div>;
};
function App() {
return (
<div className="App">
<ParamsExample />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
我创建了三个打开Miliko组件的链接。但是当我快速单击链接时,出现此错误。要修复,请取消useEffect清理功能中的所有订阅和异步任务。
答案 0 :(得分:27)
我认为问题是由异步调用完成前的卸载引起的。
bool
const useAsync = () => {
const [data, setData] = useState(null)
const mountedRef = useRef(true)
const execute = useCallback(() => {
setLoading(true)
return asyncFunc()
.then(res => {
if (!mountedRef.current) return null
setData(res)
return res
})
}, [])
useEffect(() => {
return () => {
mountedRef.current = false
}
}, [])
此处用于指示是否仍在安装组件。如果是这样,请继续执行异步调用以更新组件状态,否则,请跳过它们。
这应该是不导致内存泄漏(访问清除的内存)问题的主要原因。
答案 1 :(得分:11)
useEffect 也会尝试保持与数据获取过程的通信。由于这是一种反模式,会使您的应用程序遭受内存泄漏,因此取消对useEffect的订阅可以优化您的应用程序。
在下面的简单实现示例中,您将使用标志(已订阅)来确定何时取消订阅。效果结束时,您会打电话进行清理。
export const useUserData = () => {
const initialState = {
user: {},
error: null
}
const [state, setState] = useState(initialState);
useEffect(() => {
// clean up controller
let isSubscribed = true;
// Try to communicate with sever API
fetch(SERVER_URI)
.then(response => response.json())
.then(data => isSubscribed ? setState(prevState => ({
...prevState, user: data
})) : null)
.catch(error => {
if (isSubscribed) {
setState(prevState => ({
...prevState,
error
}));
}
})
// cancel subscription to useEffect
return () => (isSubscribed = false)
}, []);
return state
}
您可以从此博客juliangaramendy中阅读更多内容
答案 2 :(得分:1)
遵循@Niyongabo 解决方案,我最终修复它的方式是:
const mountedRef = useRef(true);
const fetchSpecificItem = useCallback(async () => {
try {
const ref = await db
.collection('redeems')
.where('rewardItem.id', '==', reward.id)
.get();
const data = ref.docs.map(doc => ({ id: doc.id, ...doc.data() }));
if (!mountedRef.current) return null;
setRedeems(data);
setIsFetching(false);
} catch (error) {
console.log(error);
}
}, [mountedRef]);
useEffect(() => {
fetchSpecificItem();
return () => {
mountedRef.current = false;
};
}, [fetchSpecificItem]);
答案 3 :(得分:1)
创建一个可变的 ref 对象并将其设置为 true,并在清理期间切换其值,以确保组件已被取消。
const mountedRef = useRef(true)
useEffect(() => {
// CALL YOUR API OR ASYNC FUNCTION HERE
return () => { mountedRef.current = false }
}, [])
答案 4 :(得分:0)
fetchData
是一个异步函数,它将返回一个Promise。但是您没有解决就调用了它。如果需要在组件卸载时进行任何清理,请在具有清理代码的效果内返回一个函数。试试这个:
const Miliko = () => {
const [data, setData] = useState({ hits: [] });
const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux');
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
(async function() {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
})();
return function() {
/**
* Add cleanup code here
*/
};
}, [url]);
return [{ data, isLoading, isError }, setUrl];
};
我建议您阅读官方的docs,在其中清楚地解释了它以及一些可配置的参数。
答案 5 :(得分:0)
如果没有@windmaomao回答,我可能要花其他时间试图弄清楚如何取消订阅。
简而言之,我分别使用了两个钩子useCallback
来记住函数,并使用useEffect
来获取数据。
const fetchSpecificItem = useCallback(async ({ itemId }) => {
try {
... fetch data
/*
Before you setState ensure the component is mounted
otherwise, return null and don't allow to unmounted component.
*/
if (!mountedRef.current) return null;
/*
if the component is mounted feel free to setState
*/
} catch (error) {
... handle errors
}
}, [mountedRef]) // add variable as dependency
我使用useEffect
来获取数据。
我不能仅仅因为不能在函数内部调用钩子而在内部调用函数。
useEffect(() => {
fetchSpecificItem(input);
return () => {
mountedRef.current = false; // clean up function
};
}, [input, fetchSpecificItem]); // add function as dependency
谢谢大家的贡献,帮助我学习了更多有关钩子的用法。
答案 6 :(得分:0)
我的案子与这个问题想要的完全不同。还是我有同样的错误。
我的情况是因为我有一个“列表”,该列表是通过使用数组中的.map
呈现的。我需要使用.shift
。 (以删除数组中的第一项)
如果数组只有一个项目,那是可以的,但是因为它有2个->第一个被“删除/移位”并且因为我使用了key={index}
(而索引来自{{1} }),它假定第二个项目(后来是第一个项目)与转移的项目相同。
React保留了第一项的信息(它们是所有节点),因此,如果第二个节点使用.map
,React引发错误,则该组件已经被卸除,因为前一个节点的索引为0,且键为key 0具有与第二个组件相同的键0。
第二个组件正确使用了useEffect()
,但是React假设它是由那个先前的节点调用的,该节点不再在现场->导致错误。
我通过添加不同的useEffect
属性值(不是索引),但是添加了一些唯一的字符串来解决此问题。