16-捕捉错误
4.2.7 捕捉错误
错误处理是编写干净的程序最重要的部分。到目前为止,我们还没有看到React中用来处理错误的任何特殊方法。如果你使用React已经有很长时间,那么可能记得,如果React组件的 render 或生命周期方法发生了错误,之前版本的React会锁定整个应用程序。这往往是挫折的根源,因为这意味着未捕获的错误可能会锁住整个应用程序。
最近版本的React引入了一个被称为错误边界的新概念来帮助解决这个问题。如果组件的构造函数、render或生命周期方法抛出未捕获的异常,React会将组件和它的子组件从DOM中卸载。这乍看起来似乎令人困惑,但它的好处是能够避免组件的错误破坏应用程序的其余部分。
练习4-2 组件间的差异
创建自抽象基类 React.Component 的组件与创建自普通函数而未经继承的组件之间的区别是什么?
可以通过使用组件从 React.Component 继承的 componentDidCatch 方法来处理这些错误。该方法的语义与JavaScript中的 try ... catch 的行为相似。 componentDidCatch 可以访问抛出的错误和错误消息。使用这些可以确保组件适当地响应错误。在大型应用程序中,可以使用该方法为单个组件(可能是小部件、卡片组件或其他组件)设置错误状态或在应用程序级别设置错误状态。代码清单4-6展示了将 componentDidCatch 方法添加到父组件中的方法。
代码清单4-6 处理错误
//...
class ChildComponent extends Component {
constructor(props) {
super(props);
console.log('ChildComponent: state');
this.oops = this.oops.bind(this); ⇽--- 绑定类方法
}
//...
oops() {
this.setState(() => ({ oops: true })); ⇽--- 切换状态以便抛出错误
}
render() {
console.log('ChildComponent: render');
if (this.state.oops) {
throw new Error('Something went wrong'); ⇽--- 在render方法中抛出错误
}
return [
<div key="name">Name: {this.props.name}</div>,
<button key="error" onClick={this.oops}>
Create error
</button>
];
}
}
class ParentComponent extends Component {
//...
constructor(props) {
super(props);
console.log('ParentComponent: state');
this.state = { text: '' };
this.onInputChange = this.onInputChange.bind(this);
}
//...
componentDidCatch(err, errorInfo) { ⇽--- 将componentDidCatch方法添加到父组件中并用它更新组件状态
console.log('componentDidCatch');
console.error(err);
console.error(errorInfo);
this.setState(() => ({ err, errorInfo }));
}
render() {
console.log('ParentComponent: render');
if (this.state.err) { ⇽--- 如果抛出错误,就显示该错误和错误信息
return (
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()} ⇽--- 如果抛出错误,就显示该错误和错误信息
<br />
{this.state.errorInfo.componentStack} ⇽--- 如果抛出错误,就显示该错误和错误信息
</details>
);
}
return [
<h2 key="h2">Learn about rendering and lifecycle methods!</h2>,
<input key="input" value={this.state.text}
onChange={this.onInputChange} />,
<ChildComponent key="ChildComponent" name={this.state.text} />
];
}
}
render(<ParentComponent />, document.getElementById('root'));
至此,我们已经了解React提供的不同生命周期方法并了解在各种情况下如何使用它们。如果这看起来好像要关注很多方法,那么了解到这些方法构成了React组件API的绝大部分(也可以将表4-1当作速查表)就让人放心了。到目前为止,React核心API并没有超出我们所讨论的范围。更重要的是,不是必须使用所有这些方法,使用需要的方法即可。表4-1展示了到目前为止所涉及方法的概要(注意,没有包含 render )。
| 初始方法 | “将执行”方法 | “已完成”方法 |
| :----- | :----- | :----- | :----- | :----- |
| 挂载 | defaultProps | 参数——无,静态属性 | 何物——多次访问的静态版本,如果属性没有被父组件赋值,就将该值赋给 this.props | 何时——当组件被创建且无法依赖this.props时被调用。返回实例间共享的复合对象,而非副本 | componentWillMount | 参数——无 | 何物——允许在加载过程发生前操作组件数据。例如,如果在这个方法中调用 setState , render() 将会看到更新的状态,而且尽管状态发生了变化, render() 也只执行一次。更改初始渲染数据的“最后机会” | 何时——调用一次,即在客户端也在服务器端(第12章会介绍服务器端渲染),在最初渲染发生之前 | componentDidMount | 参数——无 | 何物——组件插入DOM后调用一次。此时,可以访问refs(访问底层DOM表示的一种方法,将在后续章节讨论)。通常是执行“不纯”操作的好地方,如集成其他JavaScript库、设置计时器(通过 setTimeout 或者 setInterval ),或者发送HTTP请求。我们常用这个方法替换组件中的占位数据 | 何时——调用一次,只在客户端(而不在服务器端!),在最初渲染后立即调用。子组件的 component DidMount() 方法在父组件之前调用 |
| 更新 | shouldComponentUpdate | 参数—— nextProps 、 nextState | 何物——如果 shouldComponent Update 返回false,将会完全跳过 render() 直到下次状态改变。也就是说,将不会调用 componentWill Update 和 componentDidUpdate 。作为高级性能调优的应急手段特别有效 | 何时——当组件接收新属性或状态时,在渲染前调用。最初渲染不会被调用 | componentWillReceiveProps | 参数—— nextProps: Object | 何物——在通过使用 this. set-State() 更新状态而调用 render() 之前将这个方法作为响应属性转换的机会。可以使用 this.props 访问旧属性。在该函数中调用 this.setState() 不会触发额外的渲染 | 何时——当组件接收新属性时调用。最初渲染不会调用该方法 | componentWillUpdate | 参数—— nextProps: Object 、 nextState : Object | 何物——在更新前使用该方法进行准备。不能使用setState() | 何时——当接收新属性或新状态进行渲染之前立即调用。最初渲染不会调用 | componentWillUnmount | 参数——无 | 何物——在这个方法里执行任何必要的清理,如解除计时器或清理 componentDidMount 创建的任何DOM元素 | 何时——在组件卸载之前立即调用 | componentDidUpdate | 参数—— prevProps : Object 、 prevState: Object | 何物——在组件更新被刷新到DOM后立即调用。最初渲染不会调用该方法 | 何时——在组件已经更新时,将这一方法用作操作DOM的机会 |
| 错误 | componentDidCatch | 参数—— error 、 errorInfo | 何物——处理组件里的错误。React会卸载组件树中发生错误的组件以及其下的组件 | 何时——在构造函数、生命周期方法或渲染方法内发生错误时调用 |