React useEffect的cleanup
开始这篇文章前,您应该对什么useEffect是有一个基本的了解,包括可以使用它来获取数据。
本文将解释useEffect
Hook的cleanup,希望在本文结束时,您应该能够舒适地使用。
useEffect
cleanup?顾名思义,useEffect
cleanup 是Hook中的一个函数,它允许我们在卸载组件之前整理代码。
useEffect
挂钩可以返回一个函数。
cleanup可防止内存泄漏并删除一些不必要和不需要的行为。
useEffect(() => {
effect
return () => {
cleanup
}
}, [input])
useEffect
cleanup很有用?如前所述,useEffect
cleanup可帮助开发人员清理防止不需要的行为并优化应用程序的性能。
但是,需要注意的是,useEffect
cleanup不仅在组件想要卸载时运行,也可以在某些属性和状态改变后运行终止副作用。
看看这个场景:假设我们通过特定用户id
的 fetch数据,在获取完成之前,我们改变主意尝试获取另一个用户。此时id
更新,而前一个 fetch 请求仍在进行中。
使用清理函数中止请求,应用程序就不会内存泄漏。
useEffect
什么时候应该使用清理?假设我们有一个 React 组件来获取和呈现数据。如果组件在清理之前卸载,useEffect
尝试更新状态(在未安装的组件上)会得到如下所示的错误:
为了修复这个错误,我们使用了清理函数来解决它。
根据 React 的官方文档,“React 在组件卸载时执行清理。但是……副作用会在每次渲染时运行,而不仅仅是一次。这就是为什么 React 还会在下次运行副作用执行之前清除上一次渲染中的副作用。”
清理通常用于取消所有订阅和取消获取请求。
开始清理订阅,我们必须首先取消订阅,因为我们不想让应用程序内存泄漏,我们想优化应用程序。
在卸载组件之前取消订阅,将变量 , 设置isApiSubscribed
为true
,然后我们可以false
在卸载时将其设置为:
useEffect(() => {
// set our variable to true
let isApiSubscribed = true;
axios.get(API).then((response) => {
if (isApiSubscribed) {
// handle success
}
});
return () => {
// cancel the subscription
isApiSubscribed = false;
};
}, []);
在上面的代码中,我们将变量设置isApiSubscribed
为true
,然后将其用作处理我们成功请求的条件。然而,我们设置变量isApiSubscribed
来false
当我们卸载组件。
取消 fetch 请求调用的方法:AbortController
Axios 取消令牌。
使用AbortController
,我们必须使用构造函数创建一个控制器。然后,当 fetch 请求启动时,我们将作为一个选项传递到请求的对象中。AbortController() AbortSignaloption
这将控制器和信号与获取请求相关联,并使用以下命令取消它:AbortController.abort()
>useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(API, {
signal: signal
})
.then((response) => response.json())
.then((response) => {
// handle success
});
return () => {
// cancel the request before component unmounts
controller.abort();
};
}, []);
我们可以更进一步,在 catch 中添加一个错误条件,这样 fetch 请求中止时就不会抛出错误。原因是,在卸载时,我们仍会在处理错误时尝试更新状态。
我们能做的就是写一个条件,知道我们会得到什么样的错误;
如果我们收到中止错误,那么我们不更新状态:
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(API, {
signal: signal
})
.then((response) => response.json())
.then((response) => {
// handle success
console.log(response);
})
.catch((err) => {
if (err.name === 'AbortError') {
console.log('successfully aborted');
} else {
// handle error
}
});
return () => {
// cancel the request before component unmounts
controller.abort();
};
}, []);
即使我们在请求完成之前导航到另一个页面,我们也不会再次收到该错误,因为请求将在组件卸载之前中止。如果我们收到中止错误,状态也不会更新。
所以,如何使用 Axios 的取消选项,Axios 取消令牌来做同样的事情,
我们首先将来自 Axios 的数据存储在一个名为 source 的常量中,将令牌作为 Axios 选项传递,然后使用以下命令取消请求:CancelToken.source()source.cancel()
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios
.get(API, {
cancelToken: source.token
})
.catch((err) => {
if (axios.isCancel(err)) {
console.log('successfully aborted');
} else {
// handle error
}
});
return () => {
// cancel the request before component unmounts
source.cancel();
};
}, []);
就像我们对AbortError
in所做的那样AbortController
,Axios 给了我们一个调用的方法isCancel
,它允许我们检查错误的原因并知道如何处理错误。
如果请求 Axios 源中止或取消而失败,那么我们不想更新状态。
useEffect
cleanup看一个示例,说明何时会发生上述错误以及在发生时如何使用cleanup。
创建两个文件:Post
和App
.
// Post component
import React, { useState, useEffect } from "react";
export default function Post() {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
.then((res) => res.json())
.then((res) => setPosts(res))
.catch((err) => setError(err));
}, []);
return (
{!error ? (
posts.map((post) => (
- {post.title}
))
) : (
{error}
)}
);
}
这是一个简单的 post 组件,它在每次渲染时获取 post 并处理获取错误。
我们在主组件中导入 post 组件,单击按钮时显示post ,再次单击按钮隐藏post ,即挂载和卸载post 组件:
// App component
import React, { useState } from "react";
import Post from "./Post";
const App = () => {
const [show, setShow] = useState(false);
const showPost = () => {
// toggles posts onclick of button
setShow(!show);
};
return (
{show && }
);
};
export default App;
在Post 呈现之前单击该按钮,再次单击该按钮,我们会在控制台中收到错误消息。
这是因为 ReactuseEffect
仍在运行并尝试在后台获取 API。完成获取 API 后,它会尝试更新状态,但这次是在未安装的组件上,因此会引发此错误:
要清除此错误并阻止内存泄漏,我们必须使用上述解决方案来实现cleanup。在这篇文章中,我们将使用AbortController
:
// Post component
import React, { useState, useEffect } from "react";
export default function Post() {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
.then((res) => res.json())
.then((res) => setPosts(res))
.catch((err) => {
setError(err);
});
return () => controller.abort();
}, []);
return (
{!error ? (
posts.map((post) => (
- {post.title}
))
) : (
{error}
)}
);
}
我们在控制台中看到,即使在清除函数中中止请求后,卸载也会引发错误。
正如我们之前所讨论的,当我们中止 fetch 调用时会发生此错误。
useEffect
在 catch 块中捕获 fetch 错误,然后尝试更新错误状态,然后抛出错误。要停止此更新,我们可以使用条件并检查我们得到的错误类型。if
else
如果中止错误,我们不需要更新状态,否则我们处理错误:
// Post component
import React, { useState, useEffect } from "react";
export default function Post() {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
.then((res) => res.json())
.then((res) => setPosts(res))
.catch((err) => {
if (err.name === "AbortError") {
console.log("successfully aborted");
} else {
setError(err);
}
});
return () => controller.abort();
}, []);
return (
{!error ? (
posts.map((post) => (
- {post.title}
))
) : (
{error}
)}
);
}
注意这里只是在使用 fetch时调用方式
当使用 Axios 时应该判断:err.name === "AbortError" axios.isCancel()
完成!
如何使用useEffectHook的
cleanup来防止内存泄漏和优化应用程序非常重要。
希望本文对您有所帮助。