反应钩子。无法对未安装的组件执行 React 状态更新
Posted
技术标签:
【中文标题】反应钩子。无法对未安装的组件执行 React 状态更新【英文标题】:React-hooks. Can't perform a React state update on an unmounted component 【发布时间】:2019-10-19 21:46:16 【问题描述】:我收到此错误:
无法对未安装的组件执行 React 状态更新。这是 无操作,但它表明您的应用程序中存在内存泄漏。修理, 在 useEffect 清理中取消所有订阅和异步任务 功能。
当开始获取数据并卸载组件时,函数正在尝试更新卸载组件的状态。
解决此问题的最佳方法是什么?
CodePen example。
default function Test()
const [notSeenAmount, setNotSeenAmount] = useState(false)
useEffect(() =>
let timer = setInterval(updateNotSeenAmount, 2000)
return () => clearInterval(timer)
, [])
async function updateNotSeenAmount()
let data // here i fetch data
setNotSeenAmount(data) // here is problem. If component was unmounted, i get error.
async function anotherFunction()
updateNotSeenAmount() //it can trigger update too
return <button onClick=updateNotSeenAmount>Push me</button> //update can be triggered manually
【问题讨论】:
您的问题可能是您使用的 setInterval 不正确,请删除函数调用:即updateNotSeenAmount
中的 updateNotSeenAmount
内的 ()
setInterval
。
@Khauri ye,已修复,但问题仍然存在。
我认为在这里使用括号是有问题的 - 你有适当的清理代码来取消超时,所以我认为 remove 正在传递的[]
是安全的到useEffect
【参考方案1】:
这是一个简单的解决方案。这个警告是由于当我们在后台执行一些 fetch 请求时(因为一些请求需要一些时间。)并且我们从那个屏幕导航回来,然后 react 不能更新状态。这是示例代码。 在每次状态更新之前写下这一行。
if(!isScreenMounted.current) return;
这里是完整的例子
import React , useRef from 'react'
import Text,StatusBar,SafeAreaView,ScrollView, StyleSheet from 'react-native'
import BASEURL from '../constants/BaseURL';
const SearchScreen = () =>
const isScreenMounted = useRef(true)
useEffect(() =>
return () => isScreenMounted.current = false
,[])
const ConvertFileSubmit = () =>
if(!isScreenMounted.current) return;
setUpLoading(true)
var formdata = new FormData();
var file =
uri: `file://$route.params.selectedfiles[0].uri`,
type:`$route.params.selectedfiles[0].minetype`,
name:`$route.params.selectedfiles[0].displayname`,
;
formdata.append("file",file);
fetch(`$BASEURL/UploadFile`,
method: 'POST',
body: formdata,
redirect: 'manual'
).then(response => response.json())
.then(result =>
if(!isScreenMounted.current) return;
setUpLoading(false)
).catch(error =>
console.log('error', error)
);
return(
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style=styles.scrollView>
<Text>Search Screen</Text>
</ScrollView>
</SafeAreaView>
</>
)
export default SearchScreen;
const styles = StyleSheet.create(
scrollView:
backgroundColor:"red",
,
container:
flex:1,
justifyContent:"center",
alignItems:"center"
)
【讨论】:
这对我不起作用【参考方案2】:TL;DR
这是一个CodeSandBox 示例
其他答案当然有效,我只是想分享一个我想出的解决方案。 我构建了这个hook,它的工作原理与 React 的 useState 类似,但只有在安装组件时才会设置状态。我觉得它更优雅,因为您不必在组件中使用 isMounted 变量来搞乱!
安装:
npm install use-state-if-mounted
用法:
const [count, setCount] = useStateIfMounted(0);
您可以在钩子的npm page 上找到更多高级文档。
【讨论】:
它不起作用,来自 npm 站点:这个“解决方案”不能避免泄漏。即使 AbortController 似乎也不是防止内存泄漏的灵丹妙药?。 遗憾的是,这并不能解决我的问题【参考方案3】:如果你从 axios 获取数据(使用钩子)并且错误仍然发生,只需将 setter 包装在条件中
let isRendered = useRef(false);
useEffect(() =>
isRendered = true;
axios
.get("/sample/api")
.then(res =>
if (isRendered)
setState(res.data);
return null;
)
.catch(err => console.log(err));
return () =>
isRendered = false;
;
, []);
【讨论】:
为什么要在顶部加上isRendered=useRef(false)? 这个不需要let isRendered = useRef(false);
isRendered.current
【参考方案4】:
最简单的解决方案是使用一个局部变量来跟踪组件是否已安装。这是基于类的方法的常见模式。这里是an example 用钩子实现它:
function Example()
const [text, setText] = React.useState("waiting...");
React.useEffect(() =>
let isCancelled = false;
simulateSlowNetworkRequest().then(() =>
if (!isCancelled)
setText("done!");
);
return () =>
isCancelled = true;
;
, []);
return <h2>text</h2>;
这里是an alternative 和useRef
(见下文)。请注意,对于依赖项列表,此解决方案将不起作用。 ref 的值将在第一次渲染后保持为真。在这种情况下,第一个解决方案更合适。
function Example()
const isCancelled = React.useRef(false);
const [text, setText] = React.useState("waiting...");
React.useEffect(() =>
fetch();
return () =>
isCancelled.current = true;
;
, []);
function fetch()
simulateSlowNetworkRequest().then(() =>
if (!isCancelled.current)
setText("done!");
);
return <h2>text</h2>;
您可以在此article 中找到有关此模式的更多信息。这是 GitHub 上 React 项目中的 an issue,展示了这个解决方案。
【讨论】:
这是个好方法,但是如何从外部触发更新呢?间隔怎么用? 我没听懂这个问题,你能提供更多的上下文吗? @RTW 你不能在useEffect
中移动你的updateNotSeenAmount
函数?
@AndriiGolubenko 我可以,但我在外面也需要它。添加示例。
@RTW 我已经用使用useRef
的替代解决方案更新了答案。以上是关于反应钩子。无法对未安装的组件执行 React 状态更新的主要内容,如果未能解决你的问题,请参考以下文章
React-Apollo - 查询 - renderProp:反应警告:无法对未安装的组件执行反应状态更新
登录页面后反应路由器 v6 不会切换到主页:警告:无法对未安装的组件执行 React 状态更新
警告:无法对未安装的组件(多个组件)执行 React 状态更新
Expo React 警告:无法对未安装的组件执行 React 状态更新。这是一个无操作,但它表明您的应用程序中存在内存泄漏