React 15:
渲染过程中有出错,直接crash整个页面,并且错误信息不明确,可读性差.
一旦某个组件发生错误,整个组件树将会从根节点被unmount下来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class BuggyCounter extends React .Component { constructor (props) { super (props); this .state = { counter : 0 }; this .handleClick = this .handleClick.bind(this ); } componentWillMount() { throw new Error ('I am crash' ); } handleClick() { this .setState(({counter} ) => ({ counter: counter + 1 })); } render() { if (this .state.counter === 5 ) { throw new Error ('I crashed!' ); } return <h1 onClick ={this.handleClick} > {this.state.counter}</h1 > ; } } function App ( ) { return ( <div> <p> <b> This is an example of error boundaries in React 16. <br /><br /> Click on the numbers to increase the counters. <br /> The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component. </b > </p> <hr / > <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p> <BuggyCounter / > <hr /> </div> ); } ReactDOM.render( <App / >, document .getElementById('root' ) );
比如上面这个App,可以看到子组件BuggyCounter出了点问题,在没有Error Boundary的时候,整个App都会crash掉,所以显示白屏。
React 16:
React之前没有提供一种合适的处理组件错误的方法,而React16.0中通过Error Boundaries来处理组件内部的错误,从而可以修正错误组件。
用于捕获子组件树的组件异常(即错误边界只可以捕获组件在树中比他低的组件错误。),记录错误并展示一个用Error Boundary提供的内容替代错误组件。
捕获范围:
渲染期间
生命周期内
整个组件树构造函数内
如何使用:
最佳实践:
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class ErrorBoundary extends React .Component { constructor (props) { super (props); this .state = { hasError : false }; } static getDerivedStateFromError(error) { return { hasError : true }; } componentDidCatch(error, info) { logErrorToMyService(error, info); } render() { if (this .state.hasError) { return <h1 > Something went wrong.</h1 > ; } return this .props.children; } }
将ErrorBoundary抽象为一个公用的组件类, 我们可以在容易出错的组件外使用ErrorBoundary将它包裹起来。
1 2 3 <ErrorBoundary> <MyWidget /> </ErrorBoundary>
componentDidCatch()生命周期函数
componentDidCatch是一个新的生命周期函数,当组件有了这个生命周期函数,就成为了一个Error Boundaries。下面我们来看componnetDidCatch()中的参数:
1 2 componentDidCatch(error, info) { }
error参数,表示的是被抛出的错误的信息,而info是一个对象包含了组件堆栈中的信息(也就是在发生错误的子组件中层层传递错误信息,到顶层的Error Boundaries,每一层中的组件名)。
Component Stack Traces
下面我们来看组件堆栈轨迹,我们假设这样一个结构:
1 2 3 4 5 6 7 <App> <div> <ErrorBoundary> <Child></Child > </ErrorBoundary> </ div></App>
如果在Child组件中发生了js错误,那么堆栈的报错信息应该如下:
1 2 3 4 5 the error is located at : in Child (created by App) in ErrorBoundary(created by App) in div (created by App) in App
如果需要报错信息显示错误组件所在的具体的行数和位置,可以使用babel-plugin-transform-react-jsx-source插件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 class ErrorBoundary extends React .Component { constructor (props) { super (props); this .state = { error : null , errorInfo : null }; } componentDidCatch(error, errorInfo) { this .setState({ error: error, errorInfo: errorInfo }) } render() { if (this .state.errorInfo) { return ( <div> <h2>Something went wrong.</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {this.state.error && this.state.error.toString()} <br / > {this .state.errorInfo.componentStack} </details> </ div> ); } return this .props.children; } } class BuggyCounter extends React .Component { constructor (props) { super (props); this .state = { counter : 0 }; this .handleClick = this .handleClick.bind(this ); } componentWillMount() { throw new Error ('I am crash' ); } handleClick() { this .setState(({counter} ) => ({ counter: counter + 1 })); } render() { if (this .state.counter === 5 ) { throw new Error ('I crashed!' ); } return <h1 onClick ={this.handleClick} > {this.state.counter}</h1 > ; } } function App ( ) { return ( <div> <p> <b> This is an example of error boundaries in React 16. <br /><br /> Click on the numbers to increase the counters. <br /> The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component. </b > </p> <hr / > <ErrorBoundary> <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p> <BuggyCounter / > </ErrorBoundary> <hr / > </div> ); } ReactDOM.render( <App / >, document .getElementById('root' ) );
可以看到加上Error Boundary之后,除了出错的组件,其他的地方都不受影响。
而且它很清晰的告诉我们是哪个组件发生了错误。
注意事项:
Error Boundary无法捕获下面的错误:
事件函数里的错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class MyComponent extends React .Component { constructor (props) { super (props); this .state = { error : null }; this .handleClick = this .handleClick.bind(this ); } handleClick() { try { } catch (error) { this .setState({ error }); } } render() { if (this .state.error) { return <h1 > Caught an error.</h1 > } return <div onClick ={this.handleClick} > Click Me</div > } }
上面的例子中,handleClick方法里面发生的错误,Error Boundary是捕获不到的。因为它不发生在渲染阶段,所以采用try/catch来捕获。
异步代码(例如setTimeout 或 requestAnimationFrame 回调函数)
1 2 3 4 5 6 7 8 9 10 11 class A extends React .Component { render() { setTimeout(() => { throw new Error ('error' ) }, 1000 ) return ( <div></div > ) } }
服务端渲染
因为服务器渲染不支持Error Boundary
Error Boundary自身抛出来的错误 (而不是其子组件)
错误边界放在哪里?
一般来说,有两个地方:
可以放在顶层,告诉用户有东西出错。但是我个人不建议这样,这感觉失去了错误边界的意义。因为有一个组件出错了,其他正常的也没办法正常显示了
包在子组件外面,保护其他应用不崩溃。
Reference Links
本文由
Yuankun Li 创作和发表,采用
BY -
NC -
SA 国际许可协议进行许可,转载请注明作者及出处。