我认为标题说明了一切.每次卸载仍在提取的组件时都会显示黄色警告.
警告:无法在卸载的组件上调用setState(或forceUpdate).这是一个无操作,但是......要修复,取消componentWillUnmount方法中的所有订阅和异步任务.
constructor(props){
super(props);
this.state = {
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
return fetch('LINK HERE')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
Run Code Online (Sandbox Code Playgroud)
Tom*_*zyk 56
当您触发Promise时,它可能需要几秒钟才能结算,到那时用户可能已导航到您应用中的其他位置.因此,当Promise解析setState在未安装的组件上执行时,您会收到错误 - 就像您的情况一样.这也可能导致内存泄漏.
这就是为什么最好将一些异步逻辑从组件中移出.
否则,你需要以某种方式取消你的承诺.或者 - 作为最后的手段技术(它是反模式) - 你可以保留一个变量来检查组件是否仍然被挂载:
componentDidMount(){
this.mounted = true;
this.props.fetchData().then((response) => {
if(this.mounted) {
this.setState({ data: response })
}
})
}
componentWillUnmount(){
this.mounted = false;
}
Run Code Online (Sandbox Code Playgroud)
我将再次强调 - 这是一个反模式,但在你的情况下可能就足够了(就像他们对Formik实现一样).
关于GitHub的类似讨论
编辑:
这可能是我如何用Hooks解决同样的问题(只有React):
选项A:
import React, { useState, useEffect } from "react";
export default function Page() {
const value = usePromise("https://something.com/api/");
return (
<p>{value ? value : "fetching data..."}</p>
);
}
function usePromise(url) {
const [value, setState] = useState(null);
useEffect(() => {
let isMounted = true; // track whether component is mounted
request.get(url)
.then(result => {
if (isMounted) {
setState(result);
}
});
return () => {
// clean up
isMounted = false;
};
}, []); // only on "didMount"
return value;
}
Run Code Online (Sandbox Code Playgroud)
选项B:或者,useRef它的行为类似于类的静态属性,因为它在值更改时不会使组件重新呈现:
function usePromise2(url) {
const isMounted = React.useRef(true)
const [value, setState] = useState(null);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
request.get(url)
.then(result => {
if (isMounted.current) {
setState(result);
}
});
}, []);
return value;
}
// or extract it to custom hook:
function useIsMounted() {
const isMounted = React.useRef(true)
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return isMounted; // returning "isMounted.current" wouldn't work because we would return unmutable primitive
}
Run Code Online (Sandbox Code Playgroud)
示例:https://codesandbox.io/s/86n1wq2z8
hal*_*onj 17
React的友好人员建议将您的提取电话/承诺包装在可取消的承诺中.虽然该文档中没有建议使用fetch将代码与类或函数分开,但这似乎是可取的,因为其他类和函数可能需要此功能,代码重复是反模式,并且无论延迟代码如何应该被处置或取消componentWillUnmount().根据React,您可以调用cancel()包装的promise componentWillUnmount以避免在已卸载的组件上设置状态.
如果我们使用React作为指南,提供的代码看起来就像这些代码片段:
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
const cancelablePromise = makeCancelable(fetch('LINK HERE'));
constructor(props){
super(props);
this.state = {
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
cancelablePromise.
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson,
}, () => {
});
})
.catch((error) =>{
console.error(error);
});
}
componentWillUnmount() {
cancelablePromise.cancel();
}
Run Code Online (Sandbox Code Playgroud)
----编辑----
通过在GitHub上关注问题,我发现给定的答案可能不太正确.这是我使用的一个版本,它适用于我的目的:
export const makeCancelableFunction = (fn) => {
let hasCanceled = false;
return {
promise: (val) => new Promise((resolve, reject) => {
if (hasCanceled) {
fn = null;
} else {
fn(val);
resolve(val);
}
}),
cancel() {
hasCanceled = true;
}
};
};
Run Code Online (Sandbox Code Playgroud)
这个想法是通过使函数或任何你使用null的东西来帮助垃圾收集器释放内存.
小智 15
您可以使用AbortController取消获取请求。
class FetchComponent extends React.Component{
state = { todos: [] };
controller = new AbortController();
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/todos',{
signal: this.controller.signal
})
.then(res => res.json())
.then(todos => this.setState({ todos }))
.catch(e => alert(e.message));
}
componentWillUnmount(){
this.controller.abort();
}
render(){
return null;
}
}
class App extends React.Component{
state = { fetch: true };
componentDidMount(){
this.setState({ fetch: false });
}
render(){
return this.state.fetch && <FetchComponent/>
}
}
ReactDOM.render(<App/>, document.getElementById('root'))Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>Run Code Online (Sandbox Code Playgroud)
由于该职位已被打开,所以添加了“ abortable-fetch”。 https://developers.google.com/web/updates/2017/09/abortable-fetch
(来自文档:)
控制器+信号操纵满足AbortController和AbortSignal:
const controller = new AbortController();
const signal = controller.signal;
Run Code Online (Sandbox Code Playgroud)
控制器只有一种方法:
controller.abort(); 当您这样做时,它会通知信号:
signal.addEventListener('abort', () => {
// Logs true:
console.log(signal.aborted);
});
Run Code Online (Sandbox Code Playgroud)
该API由DOM标准提供,而这就是整个API。它是故意通用的,因此可以被其他Web标准和JavaScript库使用。
例如,以下是您在5秒后使获取超时的方法:
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
});
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
44395 次 |
| 最近记录: |