React useEffect 清理:如何以及何时使用它
您是否遇到过以下错误?
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
消息很简单。我们正在尝试更改组件的状态,即使它已被卸载且不可用。
发生这种情况的原因有很多,但最常见的是我们没有取消订阅 websocket 组件,或者在异步操作完成之前卸载了它。
我们怎样才能解决这个问题?
useEffect 钩子中的清理函数。
useEffect hook 的构建方式是,如果我们在方法中返回一个函数,则该函数将在组件解除关联时执行。这非常有用,因为我们可以用它来消除不必要的行为或防止内存泄漏问题。
因此,如果我们想要清理订阅,代码将如下所示:
useEffect(() => {
API.subscribe()
return function cleanup() {
API.unsubscribe()
}
})
不要更新未挂载组件的状态
一种常见的实现是在异步函数完成后更新组件状态。但是,如果组件在完成后卸载了会发生什么?如果我们无法控制,它无论如何都会尝试设置状态。
在实际场景中,我在 React Native 上遇到过这样的情况,用户可以在进程结束之前离开屏幕。
在下面的例子中,我们有一个异步函数执行一些操作,当它运行时,我希望渲染一条“正在加载”的消息。函数执行完成后,我将状态从“正在加载”改为“正在加载”,并渲染另一条消息。
function Example(props) {
const [loading, setloading] = useState(true)
useEffect(() => {
fetchAPI.then(() => {
setloading(false)
})
}, [])
return <div>{loading ? <p>loading...</p> : <p>Fetched!!</p>}</div>
}
但是,如果我们退出组件,fetchAPI 结束并设置加载状态,就会出现开头提到的错误。所以我们需要确保 fetchAPI 完成时组件仍然处于挂载状态。
function Example(props) {
const [loading, setloading] = useState(true)
useEffect(() => {
let mounted = true
fetchAPI.then(() => {
if (mounted) {
setloading(false)
}
})
return function cleanup() {
mounted = false
}
}, [])
return <div>{loading ? <p>loading...</p> : <p>Fetched!!</p>}</div>
}
这样我们就可以判断组件是否仍然挂载。只需添加一个变量,如果我们卸载该变量,该变量的值将变为 false。
额外:取消 Axios 请求
Axios 提供了取消选项,可以在请求结束前完成。除了清理功能外,此功能还有助于防止内存泄漏。
useEffect(() => {
const source = axios.CancelToken.source()
const fetchUsers = async () => {
try {
await Axios.get('/users', {
cancelToken: source.token,
})
// ...
} catch (error) {
if (Axios.isCancel(error)) {
} else {
throw error
}
}
}
fetchData()
return () => {
source.cancel()
}
}, [])
结论
useEffect hook 中的清理函数还有很多其他用法,但我希望本文能帮助您更好地了解如何以及何时使用它。
欢迎您提出任何评论或建议,我将不胜感激。