我构建了我的应用程序wir create-react-app。在最近的版本中,它在Jest中的引导测试设置中添加了一行来卸载组件(参见ReactDOM.unmountComponentAtNode(div)
)。
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
当我为我的App组件运行测试时会发出警告。
警告:只能更新已安装或安装的组件。这通常意味着您在已卸载的组件上调用了setState,replaceState或forceUpdate。这是一个无操作。
我猜:发生这种情况是因为我在componentDidMount()
中有异步请求:
fetchFoo(bar) {
fetch(SOME_URL)
.then(response => response.json())
.then(result => this.setState({ result }))
.catch(error => this.setState({ error }));
}
如果是这种情况,我如何在测试中等待异步请求最终再次卸载组件?我知道我可以在Jest测试中移除一个衬里导致这个,但我想修复它。
解决方案:解决了问题并记录了如何通过here解决问题。
答案 0 :(得分:9)
解决此问题的最简单方法是在卸载组件时使fetch()
正确取消。在componentDidMount()
中具有不自行取消的异步请求是React的不良做法,因为根据网络速度和UI交互,他们可以经常尝试更新未安装组件的状态。要么使用可取消的Promise
,要么使用this.shouldCancel
实例变量来指示要调用setState()
的天气,如下所示:
class LifecycleTest extends React.Component {
constructor(props){
super(props)
this.shouldCancel = false;
this.state = {
result:null,
err:null
}
}
componentDidMount(){
asyncTask()
.then(result => !this.shouldCancel ? this.setState({result}) : null)
.catch(err => !this.shouldCancel ? this.setState({err}) : null);
}
componentWillUnmount(){
this.shouldCancel = true
}
render(){
const {err, result} = this.state
if(err){
return <div className="err">{err}</div>
}else if (result){
return <div className="result">{result}</div>
}else{
return <div className="loading">Loading...</div>
}
}
}
也就是说,如果您真的希望在不更改源代码的情况下进行此测试,则可以模拟fetch()
以返回永不解析的'dud'Promise
。例如,添加它应该可以修复错误:
window.fetch = jest.fn(() => new Promise((accept, reject) => {}))
然而,这是一个可怕的黑客。更好的解决方案是在组件卸载时正确取消网络请求。
答案 1 :(得分:2)
您可以在React docs中看到componentDidMount()
是处理网络请求的推荐位置。
您收到此错误的原因是您在异步调用中使用setState
并且不受React生命周期的限制,因此您最终会在组件卸载后尝试设置好状态。
您处理/更正此问题的机会将在componentWillUnmount()
。这需要在fetch()
方法期间以某种方式取消您的componentWillUnmount()
请求。您需要在可取消的承诺中包装您的网络请求才能执行此操作,因为fetch()
本身不支持直接取消请求。
最终,这会阻止在卸载组件后setState()
被称为。