如何修复 React 警告:无法对未安装的组件执行 React 状态更新

How to fix React WArning : Can't perform a React state update on an unmounted component

每当执行任何与组件相关的异步任务并且该组件卸载时,React 通常会发出此警告 -

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.

我在互联网上找到了一些将 isMount 标志(通过将其与 useRef 或 useState 一起使用)用作 true 的解决方案,然后在组件卸载时将其更新为 false。但是,根据 React 网站使用 isMount 的正确解决方案是反模式吗?

https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

在 React 的未来版本中,您可能不需要修复此问题。由于 React 开发团队将在未来的版本中删除此警告。主要原因是此警告有时可能是误报。

根据 Dan Abramov 的提交 https://github.com/facebook/react/pull/22114

但是在那个版本发布之前解决这个问题的解决方案是什么 -

  1. 使用 isMountState anti-pattern - 如果有人在他的代码中检查 isMounted 来解决这个问题,那么那个人执行这个检查已经太晚了,因为这个警告表明了同样的检查由 React 完成,但失败了。

  2. 如果这个问题是因为异步调用。那么一种可能的解决方案是在您的代码中使用 AbortController API 。 AbortController API 有助于中止任何已进行的 ajax 调用。很酷的东西。对吗?

可以在此处找到更多详细信息

Abort Controller1

所以如果它是一个获取 API 可以像这样使用 AbortController API

useEffect(() => {
  const abortController = new AbortController()
  // creating an AbortController
  fetch(url, {
      signal: abortController.signal
    })
    // passing the signal to the query
    .then(data => {
      setState(data)
      // if everything went well, set the state
    })
    .catch(error => {
      if (error.name === 'AbortError') return
      // if the query has been aborted, do nothing
      throw error
    })

  return () => {
    abortController.abort()
    // stop the query by aborting on the AbortController on unmount
  }
}, [])

如果你使用的是axios,那么好消息是axios还提供了对AbortController的支持APIs -

const fetchData = async (params) => {
        setLoading(true);
        try {
            const result = await axios.request(params);
            // more code here 

        } catch (curError) {
            if (axios.isCancel(curError)) {
                return false;
            }
            // error handling code 
        }
        return null;
    };

    useEffect(() => {
        const cancelToken = axios.CancelToken;
        const source = cancelToken.source();

            fetchData({
                ...axiosParams,
                cancelToken: source.token
            });
        
        return () => {
            source.cancel("axios request cancelled");
        };
    }, []);