生命周期与异常边界
React 组件的生命周期与异常边界
生命周期
组件的生命周期分成三个状态:
- Mounting:已插入真实
- Updating:正在被重新渲染,即
- Unmounting:已移出真实
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
此外,
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
Initial Render
Props Change
State Change
这里可以看出,componentWillReceiveProps
的回调方法。在
这听起来很赞,但遗憾的是,
`boolean shouldComponentUpdate(object nextProps, object nextState)`;
如果
`var``a = { foo: ``'bar'``}; ``var``b = { foo: ``'bar'``};``a === b; ``// false`;
可以看到,数据是相同的,但它们隶属于不同对象的引用,因此返回的是
`var``a = { foo: ``'bar'``}; ``var``b = a; ``b.foo = ``'baz'``; ``a === b; ``// true`;
虽然实现一个能够进行深度对象比较的
Component Unmount
如果需要判断某个组件是否挂载,可以
总结而言,一个完整的
/**
* @jsx React.DOM
*/
var React = require("react"),
MyReactComponent = React.createClass({
// The object returned by this method sets the initial value of this.state
getInitialState: function() {
return {};
}, // The object returned by this method sets the initial value of this.props // If a complex object is returned, it is shared among all component instances
getDefaultProps: function() {
return {};
}, // Returns the jsx markup for a component // Inspects this.state and this.props create the markup // Should never update this.state or this.props
render: function() {
return <div />;
}, // An array of objects each of which can augment the lifecycle methods
mixins: [], // Functions that can be invoked on the component without creating instances
statics: {
aStaticFunction: function() {}
}, // -- Lifecycle Methods -- // Invoked once before first render
componentWillMount: function() {
// Calling setState here does not cause a re-render
}, // Invoked once after the first render
componentDidMount: function() {
// You now have access to this.getDOMNode()
}, // Invoked whenever there is a prop change // Called BEFORE render
componentWillReceiveProps: function(nextProps) {
// Not called for the initial render
// Previous props can be accessed by this.props
// Calling setState here does not trigger an an additional re-render
}, // Determines if the render method should run in the subsequent step // Called BEFORE a render // Not called for the initial render
shouldComponentUpdate: function(nextProps, nextState) {
// If you want the render method to execute in the next step
// return true, else return false
return true;
}, // Called IMMEDIATELY BEFORE a render
componentWillUpdate: function(nextProps, nextState) {
// You cannot use this.setState() in this method
}, // Called IMMEDIATELY AFTER a render
componentDidUpdate: function(prevProps, prevState) {}, // Called IMMEDIATELY before a component is unmounted
componentWillUnmount: function() {}
});
module.exports = MyReactComponent;
/**
* ------------------ The Life-Cycle of a Composite Component ------------------
*
* - constructor: Initialization of state. The instance is now retained.
* - componentWillMount
* - render
*
- [children's constructors]
*
- [children's componentWillMount and render]
*
- [children's componentDidMount]
* - componentDidMount
*
* Update Phases:
* - componentWillReceiveProps (only called if parent updated)
* - shouldComponentUpdate
* - componentWillUpdate
* - render
*
- [children's constructors or receive props phases]
* - componentDidUpdate
*
* - componentWillUnmount
*
- [children's componentWillUnmount]
*
- [children destroyed]
* - (destroyed): The instance is now blank, released by React and ready for GC.
*
* -----------------------------------------------------------------------------
*/
实例化
存在期
销毁期
函数式组件的生命周期
异常处理
在componentDidCatch(error, info)
生命周期回调来使其变为异常边界:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
然后我们就可以如常使用该组件:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
componentDidCatch()
方法就好像针对组件的 catch {}
代码块;不过try/catch
模式更多的是面向命令式代码,而
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
}
componentDidCatch(error, errorInfo) {
// Catch errors in any components below and re-render with error message
this.setState({
error: error,
errorInfo: errorInfo
}); // You can also log error messages to an error reporting service here
}
render() {
if (this.state.errorInfo) {
// Error path
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>
);
} // Normally, just render children
return this.props.children;
}
}
class BuggyCounter extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(({ counter }) => ({
counter: counter + 1
}));
}
render() {
if (this.state.counter === 5) {
// Simulate a JS error
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 />
<BuggyCounter />
</ErrorBoundary>
<hr />
<p>
These two counters are each inside of their own error boundary. So if
one crashes, the other is not affected.
</p>
<ErrorBoundary>
<BuggyCounter />
</ErrorBoundary> <ErrorBoundary>
<BuggyCounter />
</ErrorBoundary>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));