为什么我不应该使用catch()来处理ReactuseEffectAPI调用中的错误?
在 React 文档的这个页面上:
https://reactjs.org/docs/faq-ajax.html
代码注释说...
注意:在这里处理错误而不是 catch() 块很重要,这样我们就不会吞下来自组件中实际错误的异常。
...关于在第二个参数.then之后处理错误fetch。文档中的完整片段是:
useEffect(() => {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
它没有详细说明这个建议,我已经看到了许多使用 React 代码catch处理 API 调用错误的例子。我试过谷歌搜索,但找不到任何说明。
有人可以更详细地解释一下为什么我不应该catch用来处理fetch在useEffect钩子中进行API 调用时出现的错误吗?
还是在某些情况下可以这样做,而在其他情况下则不然?
“在组件中吞下异常 [...]”是什么意思?
此规则/指南适用于所有 React,还是仅适用于 API 调用?
或者只是useEffect挂钩或componentDidMount生命周期方法?
回答
我能在这方面找到的所有内容似乎都可以追溯到2016年左右的这个 github 问题。我将逐字引用那里的内容,因为之前 Stack Overflow 上似乎没有介绍过它,并且它对事情的解释非常彻底:
.then(() => {
this.setState({ loaded: true })
})
.catch(()=> {
console.log('Swallowed!')
});
您的
catch()处理程序将捕获then()
在它之前链中抛出的任何错误,包括render()由于
setState()调用而引起的错误。即使您不
setState直接使用,如果您调用的其他代码使用它(例如 Reduxdispatch()),您也可能会遇到同样的问题。如果您不想捕获由 引起的错误
setState(),而只想捕获网络故障(假设您Promise.resolve()
实际上是 afetch()),则您想改用第二个then()参数:
componentDidMount() {
Promise.resolve()
.then(() => {
this.setState({ loaded: true })
}, (err) => {
console.log('An error occurred (but not in setState!)', err);
});
}
在这种情况下,除非你
catch()在链的后面,否则错误
render()将不会被捕获,并且使用良好的 Promise polyfill(或在 Chrome 和其他浏览器中使用本机 Promises)显示。
编辑:按照@Martin 的回答,我去测试了这个,我可以确认这似乎不再是一个相关的问题。setState从 v16.0 开始,任何版本的 React 都不会捕获来自 的渲染错误,并且由于useState仅在 v16.8 中引入,这似乎不可能成为钩子的问题。
这是一个代码沙盒,它演示了旧版 React 中的原始问题。
回答
这是示例作者的复制粘贴错误。第一个示例使用基于类的组件的组件状态:this.setState()导致同步重新渲染(或者至少在 2016 年的 react v15.0 中是这种情况,不确定今天是否适用)。因此,警告注释和第二个 arg to 的使用.then(onResolve, onReject)是合理的。第二个例子使用了useState一个函数组件的钩子:setState导致没有同步重新渲染,只有异步重新渲染。因此警告注释,使用第二arg的到.then(onResolve, onReject)是没有道理的。
为了说明这一点:使用useState钩子,您可以调用多个更新程序,并且它们都只会在一次重新渲染时生效。
const [a, setA] = useState();
const [b, setB] = useState();
const [c, setC] = useState();
const updateState = () => {
setA('foo');
setB('bar');
setC('buz');
// will only cause one single re-render
}
因此使用 catch 完全没问题
useEffect(() => {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
}
)
.catch(
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])