异常处理
一、UI 渲染异常
1.1 认识
React
组件渲染过程如果有一个环节出现问题,就会导致整个组件渲染失败,那么整个组件的 UI
层都会显示不出来,这样造成的危害是巨大的,如果越靠近 APP
应用的根组件,渲染过程中出现问题造成的影响就越大,有可能直接造成白屏的情况。
为了防止渲染异常情况 React
增加了 componentDidCatch
和 static getDerivedStateFromError()
两个额外的生命周期,去挽救由于渲染阶段出现问题造成 UI
界面无法显示的情况。
Error Boundaries
(异常边界)是 React
组件,用于捕获它子组件树种所有组件产生的 js
异常,并渲染指定的兜底 UI
来替代出问题的组件。它能捕获子组件生命周期函数中的异常,包括构造函数(constructor
)和 render
函数。而不能捕获以下异常:
-
Event handlers
(事件处理函数) -
Asynchronous code
(异步代码,如setTimeout、promise.then等) -
Server side rendering
(服务端渲染) -
Errors thrown in the error boundary itself
(rather than its children)(异常边界组件本身抛出的异常)
之所以在以上异步场景中捕获不到, 是因为它们都不在渲染期间触发, 渲染期间
的 try……catch
已经执行完成, 所以并不会捕获。
1.2 工作
React
内部其实也是通过 try...catch...
形式是捕获各阶段的异常,但是只在两个阶段的特定几处进行了异常捕获,这也是为什么异常边界只能捕获到子组件在构造函数、render
函数以及所有生命周期函数中抛出的异常。其中,throwException
和 dispatch
在遍历节点时,是从异常节点的父节点开始遍历,这也是为什么异常边界组件自身的异常不会捕获并处理
1.3 实现
- App.tsx
- errorBoundary.tsx
import A from './components/A/a';
import B from './components/B/b';
import ErrorBoundary from './components/ErrorBoundary/errorBoundary';
function App() {
return (
<ErrorBoundary>
<A></A>
<B></B>
</ErrorBoundary>
);
}
export default App;
import { Component, ErrorInfo, PropsWithChildren } from 'react';
type ErrorBoundaryProps = PropsWithChildren<{
onError?: (error: Error, info: ErrorInfo) => void;
}>;
type ErrorBoundaryState = {
didCatch: boolean;
error: Error | null;
};
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { error: null, didCatch: false };
}
static getDerivedStateFromError(error: Error) {
return { error, didCatch: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
this.props?.onError?.(error, errorInfo);
}
render() {
const { didCatch, error } = this.state;
if (didCatch) {
return (
<div>
<h2>Something went wrong:</h2>
{error?.message}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
二、JS 代码异常
2.1 认识
2.2 实现
import { Component, ErrorInfo, PropsWithChildren } from 'react';
type ErrorBoundaryProps = PropsWithChildren<{
onError?: (error: Error, info: ErrorInfo) => void;
}>;
type ErrorBoundaryState = {
didCatch: boolean;
error: Error | null;
};
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { error: null, didCatch: false };
}
componentDidMount(): void {
window.addEventListener(
'error',
event => {
console.log('event error', event);
event.preventDefault();
this.setState({ error: event.error, didCatch: true });
this.props?.onError?.(event.error, { componentStack: '' });
},
true
);
window.addEventListener(
'rejectionhandled',
event => {
console.log('event rejectionhandled', event);
event.preventDefault();
this.setState({ error: event.reason, didCatch: true });
this.props?.onError?.(event.reason, { componentStack: '' });
},
false
);
window.addEventListener(
'unhandledrejection',
event => {
console.log('event unhandledrejection', event);
event.preventDefault();
this.setState({ error: event.reason, didCatch: true });
this.props?.onError?.(event.reason, { componentStack: '' });
},
false
);
}
render() {
const { didCatch, error } = this.state;
if (didCatch) {
return (
<div>
<h2>Something went wrong:</h2>
{error?.message}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
三、全局异常捕获、处理
import { Component, ErrorInfo, PropsWithChildren } from 'react';
type ErrorBoundaryProps = PropsWithChildren<{
onError?: (error: Error, info: ErrorInfo) => void;
}>;
type ErrorBoundaryState = {
didCatch: boolean;
error: Error | null;
};
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { error: null, didCatch: false };
}
static getDerivedStateFromError(error: Error) {
return { error, didCatch: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
this.props?.onError?.(error, errorInfo);
}
componentDidMount(): void {
window.addEventListener(
'error',
event => {
console.log('event error', event);
event.preventDefault();
this.setState({ error: event.error, didCatch: true });
this.props?.onError?.(event.error, { componentStack: '' });
},
true
);
window.addEventListener(
'rejectionhandled',
event => {
console.log('event rejectionhandled', event);
event.preventDefault();
this.setState({ error: event.reason, didCatch: true });
this.props?.onError?.(event.reason, { componentStack: '' });
},
false
);
window.addEventListener(
'unhandledrejection',
event => {
console.log('event unhandledrejection', event);
event.preventDefault();
this.setState({ error: event.reason, didCatch: true });
this.props?.onError?.(event.reason, { componentStack: '' });
},
false
);
}
render() {
const { didCatch, error } = this.state;
if (didCatch) {
return (
<div>
<h2>Something went wrong:</h2>
{error?.message}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;