在 React Hooks 中取消 Axios REST 调用会使用效果清理失败



我显然没有正确清理并按照我应该的方式取消 axios GET 请求。 在我的本地,我收到一条警告,上面写着

无法对未挂载的组件执行 React 状态更新。这是 无操作,但它表示应用程序中存在内存泄漏。要修复, 取消使用效果清理中的所有订阅和异步任务 功能。

在 stackblitz 上,我的代码可以工作,但由于某种原因,我无法单击按钮来显示错误。它总是显示返回的数据。

https://codesandbox.io/s/8x5lzjmwl8

请查看我的代码并找到我的缺陷。

使用AxiosFetch.js

import {useState, useEffect} from 'react'
import axios from 'axios'
const useAxiosFetch = url => {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [loading, setLoading] = useState(true)
let source = axios.CancelToken.source()
useEffect(() => {
try {
setLoading(true)
const promise = axios
.get(url, {
cancelToken: source.token,
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log(`request cancelled:${thrown.message}`)
} else {
console.log('another error happened')
}
})
.then(a => {
setData(a)
setLoading(false)
})
} catch (e) {
setData(null)
setError(e)
}
if (source) {
console.log('source defined')
} else {
console.log('source NOT defined')
}
return function () {
console.log('cleanup of useAxiosFetch called')
if (source) {
console.log('source in cleanup exists')
} else {
source.log('source in cleanup DOES NOT exist')
}
source.cancel('Cancelling in cleanup')
}
}, [])
return {data, loading, error}
}
export default useAxiosFetch

索引.js

import React from 'react';
import useAxiosFetch from './useAxiosFetch1';
const index = () => {
const url = "http://www.fakeresponse.com/api/?sleep=5&data={%22Hello%22:%22World%22}";
const {data,loading} = useAxiosFetch(url);
if (loading) {
return (
<div>Loading...<br/>
<button onClick={() => {
window.location = "/awayfrom here";
}} >switch away</button>
</div>
);
} else {
return <div>{JSON.stringify(data)}xx</div>
}
};
export default index;

这是最终代码,万一其他人回来,一切都可以正常工作。

import {useState, useEffect} from "react";
import axios, {AxiosResponse} from "axios";
const useAxiosFetch = (url: string, timeout?: number) => {
const [data, setData] = useState<AxiosResponse | null>(null);
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let unmounted = false;
let source = axios.CancelToken.source();
axios.get(url, {
cancelToken: source.token,
timeout: timeout
})
.then(a => {
if (!unmounted) {
// @ts-ignore
setData(a.data);
setLoading(false);
}
}).catch(function (e) {
if (!unmounted) {
setError(true);
setErrorMessage(e.message);
setLoading(false);
if (axios.isCancel(e)) {
console.log(`request cancelled:${e.message}`);
} else {
console.log("another error happened:" + e.message);
}
}
});
return function () {
unmounted = true;
source.cancel("Cancelling in cleanup");
};
}, [url, timeout]);
return {data, loading, error, errorMessage};
};
export default useAxiosFetch;

基于 Axios文档 cancelToken 已弃用,从 v0.22.0 开始 Axios 支持 AbortController 以 fetch API 方式取消请求:

//...
React.useEffect(() => {
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
}).catch(error => {
//...
});
return () => {
controller.abort();
};
}, []);
//...

您案例中的问题是,在快速网络上,请求会快速导致响应,并且不允许您单击该按钮。在您可以通过ChromeDevTools实现的受限网络上,您可以正确可视化此行为

其次,当您尝试使用window.location.href = 'away link'react 导航离开时,不会更改以触发/执行组件清理,因此不会触发useEffect的清理功能。

利用路由器工作

import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
import useAxiosFetch from './useAxiosFetch'
function App(props) {
const url = 'https://www.siliconvalley-codecamp.com/rest/session/arrayonly'
const {data, loading} = useAxiosFetch(url)
// setTimeout(() => {
//   window.location.href = 'https://www.google.com/';
// }, 1000)
if (loading) {
return (
<div>
Loading...
<br />
<button
onClick={() => {
props.history.push('/home')
}}
>
switch away
</button>
</div>
)
} else {
return <div>{JSON.stringify(data)}</div>
}
}
ReactDOM.render(
<Router>
<Switch>
<Route path="/home" render={() => <div>Hello</div>} />
<Route path="/" component={App} />
</Switch>
</Router>,
document.getElementById('root'),
)

您可以在慢速网络上check the demo正常工作

完全可取消的例程示例,您根本不需要任何 CancelToken(在这里玩):

import React, { useState } from "react";
import { useAsyncEffect, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";
import cpAxios from "cp-axios"; // cancellable axios wrapper
export default function TestComponent(props) {
const [text, setText] = useState("");
const cancel = useAsyncEffect(
function* () {
console.log("mount");
this.timeout(props.timeout);

try {
setText("fetching...");
const response = yield cpAxios(props.url);
setText(`Success: ${JSON.stringify(response.data)}`);
} catch (err) {
CanceledError.rethrow(err, E_REASON_UNMOUNTED); //passthrough
setText(`Failed: ${err}`);
}
return () => {
console.log("unmount");
};
},
[props.url]
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button onClick={cancel}>Abort</button>
</div>
);
}

这就是我的做法,我认为它比这里的其他答案简单得多:

import React, { Component } from "react";
import axios from "axios";
export class Example extends Component {
_isMounted = false;
componentDidMount() {
this._isMounted = true;
axios.get("/data").then((res) => {
if (this._isMounted && res.status === 200) {
// Do what you need to do
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
return <div></div>;
}
}
export default Example;

最新更新