React生命周期
React的生命周期从广义上分为挂载、渲染、卸载三个阶段,在React的整个生命周期中提供很多钩子函数在生命周期的不同时刻调用。
描述
此处描述的是使用class类组件提供的生命周期函数,每个组件都包含自己的生命周期方法,通过重写这些方法,可以在运行过程中特定的阶段执行这些方法,常用的生命周期有constructor()、render()、componentDidMount()、componentDidUpdate()、componentWillUnmount()。
挂载过程
当组件实例被创建并插入DOM中时,其生命周期调用顺序如下:
constructor()static getDerivedStateFromProps()render()componentDidMount()
在这个阶段的componentWillMount()生命周期即将过时,在新代码中应该避免使用。
更新过程
当组件的props或state发生变化时会触发更新,组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()shouldComponentUpdate()render()getSnapshotBeforeUpdate()componentDidUpdate()
在这个阶段的componentWillUpdate()、componentWillReceiveProps()生命周期即将过时,在新代码中应该避免使用。
卸载过程
当组件从DOM中移除时,组件更新的生命周期调用顺序如下:
componentWillUnmount()
错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
static getDerivedStateFromError()componentDidCatch()
生命周期
constructor()
在React组件挂载之前,会调用它的构造函数,如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数。在为React.Component子类实现构造函数时,应在其他语句之前前调用super(props),否则this.props在构造函数中可能会出现未定义的错误。
通常在React中构造函数仅用于以下两种情况:
- 通过给
this.state赋值对象来初始化内部state。 - 为事件处理函数绑定实例。
constructor(props) {
super(props);
}
static getDerivedStateFromProps()
getDerivedStateFromProps静态方法会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用,它应返回一个对象来更新state,如果返回null则不更新任何内容。此方法无权访问组件实例,如果确实需要,可以通过提取组件props的纯函数及class之外的状态,在getDerivedStateFromProps()和其他class方法之间重用代码。此外,不管原因是什么,都会在每次渲染前触发此方法。
static getDerivedStateFromProps(props, state) {}
render()
render()方法是class组件中唯一必须实现的方法,render()函数应该为纯函数,这意味着在不修改组件state的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在componentDidMount()或其他生命周期方法中执行操作,保持render()为纯函数。当render被调用时,它会检查this.props和this.state的变化并返回以下类型之一:
React元素,通常通过JSX创建,例如<div />会被React渲染为DOM节点,<MyComponent />会被React渲染为自定义组件,无论是<div />还是<MyComponent />均为React元素。- 数组或
fragments,使得render方法可以返回多个元素。 Portals,可以渲染子节点到不同的DOM子树中。- 字符串或数值类型,它们在
DOM中会被渲染为文本节点。 - 布尔类型或
null,什么都不渲染,主要用于支持返回test && <Child />的模式,其中test为布尔类型。
render() {}
componentDidMount()
componentDidMount()会在组件挂载后(即插入DOM树后)立即调用,依赖于DOM节点的初始化应该放在这里,如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法是比较适合添加订阅的地方,如果添加了订阅,请不要忘记在componentWillUnmount()里取消订阅。
你可以在componentDidMount()里直接调用setState(),它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前,如此保证了即使在render()两次调用的情况下,用户也不会看到中间状态,请谨慎使用该模式,因为它会导致性能问题。通常应该在constructor()中初始化state,如果你的渲染依赖于DOM节点的大小或位置,比如实现modals和tooltips等情况下,你可以使用此方式处理。
componentDidMount() {}
shouldComponentUpdate()
当props或state发生变化时,shouldComponentUpdate()会在渲染执行之前被调用,返回值默认为true,首次渲染或使用forceUpdate()时不会调用该方法。根据shouldComponentUpdate()的返回值,判断React组件的输出是否受当前state或props更改的影响。默认行为是state每次发生变化组件都会重新渲染,大部分情况下,你应该遵循默认行为。
此方法仅作为性能优化的方式而存在,不要企图依靠此方法来阻止渲染,因为这可能会产生bug,你应该考虑使用内置的PureComponent组件,而不是手动编写shouldComponentUpdate(),PureComponent会对props和state进行浅层比较,并减少了跳过必要更新的可能性。
如果你一定要手动编写此函数,可以将this.props与nextProps以及this.state与nextState进行比较,并返回false以告知React可以跳过更新。请注意,返回false并不会阻止子组件在state更改时重新渲染。不建议在shouldComponentUpdate()中进行深层比较或使用JSON.stringify(),这样非常影响效率,且会损害性能。目前如果shouldComponentUpdate()返回false,则不会调用UNSAFE_componentWillUpdate(),render()和componentDidUpdate()。后续版本React可能会将shouldComponentUpdate视为提示而不是严格的指令,并且当返回false时仍可能导致组件重新渲染。
shouldComponentUpdate(nextProps, nextState) {}
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate()在最近一次渲染输出(提交到DOM节点)之前调用,它使得组件能在发生更改之前从DOM中捕获一些信息(例如滚动位置),此生命周期的任何返回值将作为参数传递给componentDidUpdate(),该方法应返回snapshot的值或null。
此用法并不常见,但它可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天线程等。
getSnapshotBeforeUpdate(prevProps, prevState) {}
componentDidUpdate()
componentDidUpdate()会在更新后会被立即调用,首次渲染不会执行此方法。当组件更新后,可以在此处对DOM进行操作,如果你对更新前后的props进行了比较,也可以选择在此处进行网络请求(例如,当props未发生变化时,则不会执行网络请求。如果shouldComponentUpdate()返回值为false,则不会调用componentDidUpdate()。
你也可以在componentDidUpdate()中直接调用setState(),但请注意它必须被包裹在一个条件语句里,否则会导致死循环,因为他将无限次触发componentDidUpdate()。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。
如果组件实现了getSnapshotBeforeUpdate()生命周期(不常用),则它的返回值将作为componentDidUpdate()的第三个参数snapshot参数传递,否则此参数将为undefined。
componentDidUpdate(prevProps, prevState, snapshot) {}
componentWillUnmount()
componentWillUnmount()会在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作,例如清除timer、取消网络请求或清除在componentDidMount()中创建的订阅等。
componentWillUnmount()中不应调用setState(),因为该组件将永远不会重新渲染,组件实例卸载后,将永远不会再挂载它。
componentWillUnmount() {}
static getDerivedStateFromError()
此生命周期会在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新state。getDerivedStateFromError()会在渲染阶段调用,因此不允许出现副作用,如遇此类情况,请改用componentDidCatch()。
static getDerivedStateFromError(error) {}
componentDidCatch()
此生命周期在后代组件抛出错误后被调用,componentDidCatch()会在提交阶段被调用,因此允许执行副作用,它应该用于记录错误之类的情况它接收两个参数:
error: 抛出的错误。info: 带有componentStack key的对象,其中包含有关组件引发错误的栈信息。
componentDidCatch(error, info) {}
示例
React组件的常用生命周期示例。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React生命周期</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
console.log("ComponentDidMount", this);
console.log(this.props);
console.log(this.state);
console.log("");
this.timer = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
console.log("ComponentWillUnmount", this);
console.log(this.props);
console.log(this.state);
console.log("");
clearInterval(this.timer);
}
tick() {
this.setState({ date: new Date() });
}
render() {
return (
<div>
<h1>{this.props.tips}</h1>
<h2>Now: {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
showClock: true,
tips: "Hello World!"
}
}
componentDidUpdate(prevProps, prevState) {
console.log("ComponentDidUpdate", this);
console.log(this.props);
console.log(this.state);
console.log("");
}
updateTips() {
this.setState((state, props) => ({
tips: "React update"
}));
}
changeDisplayClock() {
this.setState((state, props) => ({
showClock: !this.state.showClock
}));
}
render() {
return (
<div>
{this.state.showClock && <Clock tips={this.state.tips} />}
<button onClick={() => this.updateTips()}>更新tips</button>
<button onClick={() => this.changeDisplayClock()}>改变显隐</button>
</div>
);
}
}
var vm = ReactDOM.render(
<App />,
document.getElementById("root")
);
</script>
</html>
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://www.jianshu.com/p/b331d0e4b398
https://www.cnblogs.com/soyxiaobi/p/9559117.html
https://zh-hans.reactjs.org/docs/react-component.html
https://zh-hans.reactjs.org/docs/state-and-lifecycle.html
https://www.runoob.com/react/react-component-life-cycle.html
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/