为什么需要错误处理
我们都不希望自己的代码出问题,但是现实总是事与愿违。如果不进行错误处理,页面上就会出现error的场景,非常显眼。这是测试会说:小伙子,你的代码报错了。作为一个有追求的程序员,我们怎么可能承认自己的代码报错。
为了提升用户体验(为了避免尴尬),我们必须优雅的处理它,让用户看到一个优雅的错误页面(让用户看不出来是代码错误),然后(偷偷地)修复它。
所以我们必须要进行错误处理。
错误处理方式
try catch
try catch 捕获错误是我们最常用的方式。所以我们经常这样用:
方式一:
const component = () => {
const [error, setError] = useState(false)
useEffect(() => {
try {
// 请求数据, async await
} catch(e) {
console.log(e)
setError(e)
}
}, [])
if (error) return <div>页面异常了...</div>
return <div>page</div>
}
方式二:
const component = () => {
try {
// 在组件渲染期间,状态计算等等。。。
} catch(e) {
console.log(e)
return <ErrorPage>
}
return <div>page</div>
}
try catch 存在一些局限性:
- 无法捕获后代组件错误
- 不能捕获异步任务错误
ErrorBoundary
另一种在React中常见的错误处理方式就是:错误边界处理。错误边界可以处理React生命周期内发生的错误。所以他可以处理子组件错误和useEffect内部错误。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
error: false
}
}
static getDerivedStateFromError(error) {
return { error: true };
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo)
}
render() {
if (this.state.error) {
return <>error!!!</>
}
return this.props.children;
}
}
使用方式:
const App = () => {
return (
<ErrorBoundary>
<Component />
</ErrorBoundary>
)
}
错误边界也有自己的限制
不能捕获的错误:
- promise异常
- setTimeout, 各种回调函数的错误
- 事件处理过程中错误
const Component = () => {
useEffect(() => {
// 可以捕获
throw new Error('哈哈哈!');
}, [])
const onClick = () => {
// 不可以捕获
throw new Error('哈哈哈!');
}
useEffect(() => {
// promise 请求出错,不能捕获
fetch('/bla')
}, [])
return <button onClick={onClick}>click me</button>
}
const App = () => {
return (
<ErrorBoundary>
<Component />
</ErrorBoundary>
)
}
怎样在ErrorBoundary中处理异步等其他错误
在React GitHub的issue种有一个hack方法:使用try catch,然后再catch中触发React的重新渲染,从而进入React生命周期中,这样ErrorBoundary就可以捕获到错误了。
异步错误
const Component = () => {
const [state, setState] = useState();
const onClick = () => {
try {
// 发生错误
} catch (e) {
// 触发state更新
setState(() => {
throw e;
})
}
}
}
封装成一个自定义hook,方便使用:
const useCatchAsyncError = () => {
const [error, setError] = useState()
return error => {
setError(() => throw error)
}
}
// 使用
const Component = () => {
const setError = useCatchAsyncError();
useEffect(() => {
fetch('/abcd').then().catch((e) => {
setError(e)
})
})
}
回调函数等错误
const useCatchCallbackError = (callback) => {
const [state, setState] = useState();
return (...args) => {
try {
callback(...args);
} catch(e) {
setState(() => throw e);
}
}
}
// 使用
const Component = () => {
const onClick = () => {
throw new Error('哈哈哈!');
}
const onClickWithCatchError = useCatchCallbackError(onClick);
return <button onClick={onClickWithCatchError}>点我一下试试!</button>
}
总结
- try/catch 可以捕获useEffect内部的错误,但不会捕获异步代码和事件处理程序中的错误
- ErrorBoundary可以捕获React是生命周期中发生的错误,不能捕获promise等回调函数中的错误
- 捕获promise等回调函数中的错误,只需要用 ErrorBoundary ,然后让它们重新进入React 生命周期 ,使用try/catch捕获
reference
hack方法:github.com/facebook/re…