# React在未挂载组件后更新状态的错误
Table of Contents
React 组件在卸载后,如果组件内有正在处理的异步函数。那么在函数处理后,需要更新组件的状态,会得到一个警告。
出现的原因
最可能出现在以下几个情况:
- 在组件内调用了网络请求,网络请求未响应就卸载了组件
- 在组件内调用了定时器,定时器还没执行就卸载了组件
网络请求
最常见的情况就是在网络请求中,当我们发起的网络请求还未返回,我们切换了组件,导致之前的组件被卸载掉。
import { useEffect, useState } from 'react'type Post = { id: number | string; title: string }
const Posts = () => { const [posts, setPosts] = useState<Post[]>([]) useEffect(() => { const fetchData = async () => { try { const response = await fetch('https://jsonplaceholder.typicode.com/posts') const data = await response.json() setPosts(data) } catch (e) { console.log(e) } }
fetchData() }, []) return ( <ul> {posts.map((post) => { return <li key={post.id}>{post.title}</li> })} </ul> )}
export default Postsimport { useState } from 'react'import Posts from './Posts'
export default function App() { const [showPost, setShowPost] = useState(false) return ( <div> <button onClick={() => setShowPost(true)}>Show Post</button> <button onClick={() => setShowPost(false)}>Hide Post</button> {showPost && <Posts />} </div> )}https://codesandbox.io/s/fetch-call-1xt2tb
在上面的 Posts 组件挂载后,会发起向 JSON Placeholder 的 API 请求,并在请求成功后展示博客列表。
当我们点击 ‘Fetch Posts’ 按钮后,并且迅速点击 ‘Hide Posts’(也可以通过开发者工具,将 ‘Network’ 调到慢速,防止网速过快)。在网络请求成功返回之前,我们切换组件的状态,就会得到相应的错误。
定时器
另一种很常见的情况就是 setTimeout 和 setInterval 定时器的使用。
import { useState } from 'react'
const Timeout = () => { const [counter, setCounter] = useState(0) setTimeout(() => setCounter(counter + 1), 5000) return ( <> <div>Counter: {counter}</div> <div>The Timeout will add after 5 seconds.</div> </> )}
export default Timeouthttps://codesandbox.io/s/settimeout-0c5ro1
当 Timeout 组件挂载后,会设置 5s 后更新 counter 变量,期间我们卸载掉组件,就会得到相应的错误。
解决方案
以上就是常见的两种遇到该警告提示的情况。解决方案有以下方案可以参考。
更新前检测组件状态
使用 useRef 关联组件的生命周期,当组件挂载时设置变量为 true,当卸载之后设置变量为 false。在异步函数更新状态的时候,先判断 isMounted.current 的变量值。
const isMounted = useRef();
useEffect(() => { isMounted.current = true; return () => { isMounted.current = false; };}, []);
if (isMounted.current) { setState(..);}https://codesandbox.io/s/settimeout-useref-zjnr7c
可以将上面的 useRef 代码封装成自定义的 hook,也可以直接使用第三方封装的,如 react-use 的 useAsync
import { useAsync } from 'react-use'type Post = { id: number | string; title: string }
const Posts = () => { const state = useAsync(async () => { try { const response = await fetch('https://jsonplaceholder.typicode.com/posts') const data = await response.json() return data } catch (e) { console.log(e) } }, [])
return ( <ul> {state.value && state.value.map((post: Post) => { return <li key={post.id}>{post.title}</li> })} </ul> )}
export default Postshttps://codesandbox.io/s/fetch-call-useasync-evtz0n
卸载时处理异步回调
可以在卸载组件时,处理掉异步调用函数的组件。
对于接口的调用,我们可以通过调用 AbortController 的接口,取消网络请求。
import { useEffect, useState } from 'react'type Post = { id: number | string; title: string }
const Posts = () => { const [posts, setPosts] = useState<Post[]>([]) useEffect(() => { const controller = new AbortController() const signal = controller.signal const fetchData = async () => { try { const response = await fetch('https://jsonplaceholder.typicode.com/posts', { signal: signal, }) const data = await response.json() setPosts(data) } catch (e) { console.log(e) } }
fetchData() return () => { controller.abort() } }, []) return ( <ul> {posts.map((post) => { return <li key={post.id}>{post.title}</li> })} </ul> )}
export default Postshttps://codesandbox.io/s/fetch-call-abortcontroller-x0c5ro
对于 fetch, axios 均可以使用 AbortController 来进行管理卸载,当请求时,附带参数 AbortSignal,卸载组件时,调用 abort() 方法。
对于定时器的取消,则是调用 clearTimeout 和 clearInterval 来取消定时器。
import { useEffect, useState } from 'react'
const Timeout = () => { const [counter, setCounter] = useState(0) useEffect(() => { const timerID = setTimeout(() => setCounter((counter) => counter + 1), 5000) return () => clearTimeout(timerID) }, []) return ( <> <div>Counter: {counter}</div> <div>The Counter will add after 5 seconds.</div> </> )}
export default Timeouthttps://codesandbox.io/s/settimeout-cleartimeout-yhup3u
最后
以上就是常见的警告提示以及对应的解决方案。
但是,最后,在 React 18 版本中,取消了这个警告提示!!!
https://codesandbox.io/s/settimeout-react-18-w22v5u
具体取消原因可以查看 Update to remove the “setState on unmounted component” warning #82,大致意思大多数情况并不会真正造成内存泄漏。