Reactjs 要修复此问题,请取消useEffect清理函数中的所有订阅和异步任务

Reactjs 要修复此问题,请取消useEffect清理函数中的所有订阅和异步任务,reactjs,Reactjs,我有这个密码 import ReactDOM from "react-dom"; import React, { useState, useEffect } from "react"; import { BrowserRouter as Router, Route, Link } from "react-router-dom"; function ParamsExample() { return ( <Router&

我有这个密码

import ReactDOM from "react-dom";
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

function ParamsExample() {
  return (
    <Router>
      <div>
        <h2>Accounts</h2>
        <Link to="/">Netflix</Link>
        <Route path="/" component={Miliko} />
      </div>
    </Router>
  );
}

const Miliko = ({ match }) => {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    (async function() {
      setIsError(false);
      setIsLoading(true);
      try {
        const Res = await fetch("https://foo0022.firebaseio.com/New.json");
        const ResObj = await Res.json();
        const ResArr = await Object.values(ResObj).flat();
        setData(ResArr);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    })();
    console.log(data);
  }, [match]);
  return <div>{`${isLoading}${isError}`}</div>;
};

function App() {
  return (
    <div className="App">
      <ParamsExample />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
从“react-dom”导入ReactDOM;
从“React”导入React,{useState,useffect};
从“react Router dom”导入{BrowserRouter as Router,Route,Link};
函数ParamsExample(){
返回(
账户
网飞公司
);
}
const Miliko=({match})=>{
const[data,setData]=useState([]);
const[isLoading,setIsLoading]=useState(false);
const[isError,setIsError]=useState(false);
useffect(()=>{
(异步函数(){
setIsError(假);
设置加载(真);
试一试{
const Res=等待获取(“https://foo0022.firebaseio.com/New.json");
const ResObj=await Res.json();
const ResArr=await Object.values(ResObj.flat();
setData(ResArr);
}捕获(错误){
setIsError(真);
}
设置加载(假);
})();
控制台日志(数据);
},[匹配];
返回{${isLoading}${isError}`};
};
函数App(){
返回(
);
}
const rootElement=document.getElementById(“根”);
render(,rootElement);
我创建了三个链接来打开
Miliko
组件。但当我快速点击链接时,我会出现以下错误:

若要修复,请取消useEffect中的所有订阅和异步任务 清理功能


fetchData
是一个异步函数,它将返回一个承诺。但是你调用了它却没有解决它。如果需要在卸载组件时执行任何清理,请在包含清理代码的效果中返回一个函数。试试这个:

const Miliko = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux');
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    (async function() {
      setIsError(false);
      setIsLoading(true);
      try {
        const result = await axios(url);
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    })();

    return function() {
      /**
       * Add cleanup code here
       */
    };
  }, [url]);

  return [{ data, isLoading, isError }, setUrl];
};

我建议阅读官方文件,其中有详细的解释,还有一些更可配置的参数。

我认为问题是在异步调用完成之前卸载造成的

constuseasync=()=>{
const[data,setData]=useState(null)
常量mountedRef=useRef(真)
const execute=useCallback(()=>{
设置加载(真)
返回asyncFunc()
。然后(res=>{
如果(!mountedRef.current)返回null
设置数据(res)
返回res
})
}, [])
useffect(()=>{
return()=>{
mountedRef.current=false
}
}, [])
}
mountedRef
用于指示组件是否仍在安装。如果是,则继续异步调用以更新组件状态,否则,跳过它们

这应该是不出现内存泄漏(访问清理内存)问题的主要原因

更新 以上答案引出了我们在团队中使用的以下组件

/**
 * A hook to fetch async data.
 * @class useAsync
 * @borrows useAsyncObject
 * @param {object} _                props
 * @param {async} _.asyncFunc         Promise like async function
 * @param {bool} _.immediate=false    Invoke the function immediately
 * @param {object} _.funcParams       Function initial parameters
 * @param {object} _.initialData      Initial data
 * @returns {useAsyncObject}        Async object
 * @example
 *   const { execute, loading, data, error } = useAync({
 *    asyncFunc: async () => { return 'data' },
 *    immediate: false,
 *    funcParams: { data: '1' },
 *    initialData: 'Hello'
 *  })
 */
const useAsync = (props = initialProps) => {
  const {
    asyncFunc, immediate, funcParams, initialData
  } = {
    ...initialProps,
    ...props
  }
  const [loading, setLoading] = useState(immediate)
  const [data, setData] = useState(initialData)
  const [error, setError] = useState(null)
  const mountedRef = useRef(true)

  const execute = useCallback(params => {
    setLoading(true)
    return asyncFunc({ ...funcParams, ...params })
      .then(res => {
        if (!mountedRef.current) return null
        setData(res)
        setError(null)
        setLoading(false)
        return res
      })
      .catch(err => {
        if (!mountedRef.current) return null
        setError(err)
        setLoading(false)
        throw err
      })
  }, [asyncFunc, funcParams])

  useEffect(() => {
    if (immediate) {
      execute(funcParams)
    }
    return () => {
      mountedRef.current = false
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return {
    execute,
    loading,
    data,
    error
  }
}
即使组件已卸载,useEffect也会尝试与数据获取过程保持通信。由于这是一种反模式,并且会导致应用程序内存泄漏,因此取消订阅useEffect会优化应用程序

在下面的简单实现示例中,您将使用标志(isSubscribed)来确定何时取消订阅。在效果结束时,你会打电话进行清理

export const useUserData=()=>{
常量初始状态={
用户:{},
错误:null
}
常量[状态,设置状态]=使用状态(初始状态);
useffect(()=>{
//清理控制器
让isSubscribed=true;
//尝试与服务器API通信
获取(服务器URI)
.then(response=>response.json())
.然后(数据=>IssuSubscribed?设置状态(prevState=>({
…状态,用户:数据
})):null)
.catch(错误=>{
如果(已订阅){
设置状态(prevState=>({
…国家,
错误
}));
}
})
//取消订阅以生效
return()=>(isSubscribed=false)
}, []);
返回状态
}

如果没有@windmaomao的回答,你可以从这个博客上读到更多的内容,我可以花上几个小时来想办法取消订阅

简而言之,我分别使用了两个钩子
useCallback
来记忆函数和
useffect
来获取数据

const fetchSpecificItem=useCallback(异步({itemId})=>{
试一试{
…获取数据
/* 
设置状态之前,请确保已安装部件
否则,返回null,不允许卸载组件。
*/
如果(!mountedRef.current)返回空值;
/*
如果安装了部件,请随意设置状态
*/
}捕获(错误){
…处理错误
}
},[mountedRef])//将变量添加为依赖项
我使用
useffect
获取数据

我不能调用函数的内部效果,因为不能在函数内部调用钩子

useffect(()=>{
获取特定项目(输入);
return()=>{
mountedRef.current=false;//清除函数
};
},[input,fetchSpecificItem]);//将函数添加为依赖项

谢谢大家,你们的贡献帮助我更多地了解了钩子的用法。

我的案例与这个问题的目的大不相同。但我还是犯了同样的错误

我的情况是因为我有一个“列表”,它是使用数组中的
.map
呈现的。我需要使用
.shift
。(删除数组中的第一项)

如果数组只有一个项目,这是正常的,但由于它有两个项目->第一个项目被“删除/移位”,并且因为我使用了
键={index}
(而索引来自
.map
),它假设第二个项目(后来是第一个)与移位项目是同一个组件

React保留了来自第一个项目的信息(它们都是节点),因此,如果第二个节点使用了
useffect()
,React抛出错误,说明该组件已经卸载,因为索引为0且键为0的前一个节点与第二个组件具有相同的键0

第二个组件正确使用了
useffect
,但假设,