组件从创建到销毁的过程,被称为组件的生命周期。
在没有 Hooks 之前,函数组件没有生命周期,因为生命周期函数是 React.Component 类的方法实现的,函数组件没有继承 React.Component,所以也就没有生命周期。
在生命周期的整个过程,分成三个阶段:
- 挂载阶段(Mount):组件第一次在 DOM 树中被渲染的过程。
- 更新阶段(Update):组件状态发生变化,重新渲染的过程。
- 卸载阶段(Unmount):组件从 DOM 树中被移除的过程。
在生命周期的各个阶段都有相对应的钩子函数,会在特定的时机被调用,被称为组件的生命周期函数。
生命周期函数 = 生命周期钩子 = 生命周期回调函数 = 生命周期钩子函数
组件的生命周期函数(新):
新的生命周期钩子增加了 getDerivedStateFromProps 和 getSnapshotBeforeUpdate。
-
constructor()
:在组件挂载之前被调用。不能在
constructor()
构造方法里调用setState()
, 因为此时第一次render()
还未执行,也就意味 DOM 节点还未挂载。 -
render()
:是类组件中唯一必须实现的方法,用于渲染 DOM。 -
componentDidMount()
:在组件挂载后 (插入 DOM 树后) 被调用,此生命周期是发送网络请求、开启定时器、订阅消息、操作 DOM 等的好时机。可以在此钩子函数里调用
setState()
。 -
componentDidUpdate(prevProps, prevState, snapshot)
:在组件更新后被调用。首次渲染不会执行。
componentDidUpdate()
的第一个参数是 上一次的 props 值,第二个参数是 上一次的 state 值,第三个参数是getSnapshotBeforeUpdate()
的返回值。可以比较前后 props 来进行
setState()
, 否则总是setState()
会导致死循环。 -
componentWillUnmount()
:在组件卸载及销毁之前被调用。此生命周期是取消网络请求、清理定时器、取消订阅等操作的好时机。 -
static getDerivedStateFromProps(nextProps, prevState)
:在每次调用render()
方法之前都会被调用,在初始化和更新时都会被调用。
getDerivedStateFromProps()
的第一个参数为即将更新的 props,第二个参数为上一个状态的 state,可以比较 props 和 state 来加一些限制条件,防止无用的 state 更新。
getDerivedStateFromProps()
的返回值是必须的。返回一个对象来更新 state,如果不需要更新,返回 null 即可。getDerivedStateFromProps()
是用于根据属性的变化计算并返回新的状态的生命周期方法,适用于 state 的值在任何时候都取决于 props 的情况。它被设计为一个纯粹的函数,用于派生状态,最好不要在其中执行副作用操作// 之前使用 componentWillReceiveProps componentWillReceiveProps(nextProps) { if (nextProps.location.search !== this.props.location.search) { this.init() } } // 现在使用 getDerivedStateFromProps:相当于把 componentWillReceiveProps 拆分成 getDerivedStateFromProps 和 componentDidUpdate static getDerivedStateFromProps(nextProps, prevState) { const {search} = nextProps.location if (search !== prevState.search) { return { search, } } return null } componentDidUpdate(prevProps, prevState) { const {search} = this.state if (search !== prevState.search) { this.init() } }
-
shouldComponentUpdate(nextProps, nextState)
:在组件更新之前被调用,可以控制组件是否进行更新, 返回 true 时组件更新, 返回 false 则不更新。不写此生命周期钩子时默认为 true。
shouldComponentUpdate()
的第一个参数是即将更新的 props 值,第二个参数是即将更新后的 state 值。可以根据更新前后的 props 或 state 来比较加一些限制条件,决定是否更新,进行性能优化。不要在 shouldComponentUpdate 中调用
setState()
,否则会导致无限循环调用更新、渲染,直至浏览器内存崩溃。 -
getSnapshotBeforeUpdate(prevProps, prevState)
:在最新的渲染数据提交给 DOM 前会调用,也就是说,在render()
之后,在componentDidUpdate()
之前调用。使得组件可以在更新之前获取快照值。它可以使组件在 DOM 真正更新之前捕获一些信息(例如滚动位置等),此生命周期返回的任何值都会作为参数传递给
componentDidUpdate()
,返回值是必须的,如不需要传递任何值,那么返回 null。
组件的生命周期流程(新):
// Home.jsx
import React, {Component} from 'react'
class Home extends Component {
state = {
message: 'Hello Home',
}
constructor(props) {
super(props)
console.log('Parent constructor')
}
componentDidMount() {
console.log('Parent componentDidMount')
}
shouldComponentUpdate() {
console.log('Parent shouldComponentUpdate')
return true
}
componentDidUpdate() {
console.log('Parent componentDidUpdate')
}
static getDerivedStateFromProps() {
console.log('Parent getDerivedStateFromProps')
return null
}
getSnapshotBeforeUpdate() {
console.log('Parent getSnapshotBeforeUpdate')
}
handleClick = () => {
this.setState({message: 'Hello React'})
}
render() {
console.log('Parent render')
return (
<div>
<h1 onClick={this.handleClick}>{this.state.message}</h1>
</div>
)
}
}
export default Home
组件的挂载流程:
组件的更新流程:
组件的卸载流程:
- componentWillUnmount
父子组件的生命周期流程(新):
当父子组件的生命周期函数都会被触发时,会先触发父组件 render()
及 render()
之前的生命周期函数;然后触发子组件 render()
及 render()
之前的的生命周围函数;然后交替触发子父组件 render()
之后的生命周期函数,总是子组件的先被触发。
Home.jsx
import React, {Component} from 'react'
import Utils from './Utils.jsx'
class Home extends Component {
state = {
homeMessage: 'Hello Home',
}
constructor(props) {
super(props)
console.log('Parent constructor')
}
componentDidMount() {
console.log('Parent componentDidMount')
}
shouldComponentUpdate() {
console.log('Parent shouldComponentUpdate')
return true
}
componentDidUpdate() {
console.log('Parent componentDidUpdate')
}
static getDerivedStateFromProps() {
console.log('Parent getDerivedStateFromProps')
return null
}
getSnapshotBeforeUpdate() {
console.log('Parent getSnapshotBeforeUpdate')
}
handleClick = () => {
this.setState({homeMessage: 'Hello React'})
}
handleChange = (val) => {
this.setState({homeMessage: val})
}
render() {
const {homeMessage} = this.state
console.log('Parent render')
return (
<div>
<h1 onClick={this.handleClick}>{homeMessage}</h1>
<Utils homeMessage={homeMessage} onChange={this.handleChange} />
</div>
)
}
}
export default Home
Utils.jsx
import React, {Component} from 'react'
class Utils extends Component {
state = {
utilsMessage: 'Hello Utils',
}
constructor(props) {
super(props)
console.log('Child constructor')
}
componentDidMount() {
console.log('Child componentDidMount')
}
shouldComponentUpdate() {
console.log('Child shouldComponentUpdate')
return true
}
componentDidUpdate() {
console.log('Child componentDidUpdate')
}
static getDerivedStateFromProps() {
console.log('Child getDerivedStateFromProps')
return null
}
getSnapshotBeforeUpdate() {
console.log('Child getSnapshotBeforeUpdate')
}
handleClick = () => {
this.setState({utilsMessage: 'Hello JS'})
}
hanldeMouseEnter = () => {
this.setState({utilsMessage: 'Hello Web'})
this.props.onChange('Hello Web')
}
render() {
console.log('Child render')
return (
<div>
<h2 onClick={this.handleClick} onMouseLeave={this.hanldeMouseEnter}>{this.state.utilsMessage}</h2>
<h2>{this.props.homeMessage}</h2>
</div>
)
}
}
export default Utils
父子组件的挂载流程:
修改父组件时父子组件的更新流程:
当父组件状态变化时,不会是否有传递给子组件 props 或者传递给子组件的 props 是否有变化,都会会触发自身和子组件对应的生命周期。
除非使用
React.PureComponent
或者React.memo()
,那样的话,如果没有传递给子组件 props 或者传递给子组件的 props 没有变化,除了子组件的getDerivedStateFromProps()
生命周期函数会被触发外,子组件其他的生命周期函数将不会被触发。
修改子组件不影响父组件时的更新流程:
当子组件自身的 state 状态改变,不会对父组件产生副作用的情况下,父组件不会进行更新,也就是不会触发父组件的生命周期。
修改子组件影响父组件时的更新流程:
当子组件自身的 state 状态改变,对父组件产生副作用的情况下,父组件会进行更新,也就是会触发父组件的生命周期。
组件的生命周期函数(旧):
React 从 v16.3 开始废弃 componentWillMount、componentWillReceiveProps、componentWillUpdate 三个钩子函数。在新版本中使用需要加上 UNSAFE_
前缀,否则会触发控制台的警告。
UNSAFE 不是指安全性,而是表示使用这些生命周期的代码在 React 的未来版本中更有可能出现 Bug,尤其是在启用异步渲染之后。
- componentWillMount:组件挂载之前调用。
- componentWillReceiveProps:子组件接收到父组件新的 props 之前调用。
子组件第一次接收到父组件的 props 时不会调用。
只要父组件重新渲染了,无论什么情况下,子组件的 componentWillReceiveProps 就会被触发。componentWillReceiveProps(nextProps) { // 可以和 this.props 中的数据进行对比,以决定是否要执行某些方法 }
- componentWillUpdate:组件更新之前调用。
组件的生命周期流程(旧):
// Home.jsx
import React, {Component} from 'react'
class Home extends Component {
state = {
message: 'Hello Home',
}
constructor(props) {
super(props)
console.log('Parent constructor')
}
componentWillMount() {
console.log('Parent componentWillMount')
}
componentDidMount() {
console.log('Parent componentDidMount')
}
shouldComponentUpdate() {
console.log('Parent shouldComponentUpdate')
return true
}
componentWillUpdate() {
console.log('Parent componentWillUpdate')
}
componentDidUpdate() {
console.log('Parent componentDidUpdate')
}
handleClick = () => {
this.setState({message: 'Hello React'})
}
render() {
console.log('Parent render')
return (
<div>
<h1 onClick={this.handleClick}>{this.state.message}</h1>
</div>
)
}
}
export default Home
组件的挂载流程:
组件的更新流程:
组件的卸载流程:
- componentWillUnmount
父子组件的生命周期流程(旧):
当父子组件的生命周期函数都会被触发时,会先触发父组件 render()
及 render()
之前的生命周期函数;然后触发子组件 render()
及 render()
之前的的生命周围函数;然后交替触发子父组件 render()
之后的生命周期函数,总是子组件的先被触发。
// Home.jsx
import React, {Component} from 'react'
import Utils from './Utils.jsx'
class Home extends Component {
state = {
homeMessage: 'Hello Home',
}
constructor(props) {
super(props)
console.log('Parent constructor')
}
componentWillMount() {
console.log('Parent componentWillMount')
}
componentDidMount() {
console.log('Parent componentDidMount')
}
shouldComponentUpdate() {
console.log('Parent shouldComponentUpdate')
return true
}
componentWillUpdate() {
console.log('Parent componentWillUpdate')
}
componentDidUpdate() {
console.log('Parent componentDidUpdate')
}
handleClick = () => {
this.setState({homeMessage: 'Hello React'})
}
handleChange = (val) => {
this.setState({homeMessage: val})
}
render() {
const {homeMessage} = this.state
console.log('Parent render')
return (
<div>
<h1 onClick={this.handleClick}>{homeMessage}</h1>
<Utils homeMessage={homeMessage} onChange={this.handleChange} />
</div>
)
}
}
export default Home
// Utils.jsx
import React, {Component} from 'react'
class Utils extends Component {
state = {
utilsMessage: 'Hello Utils',
}
constructor(props) {
super(props)
console.log('Child constructor')
}
componentWillMount() {
console.log('Child componentWillMount')
}
componentDidMount() {
console.log('Child componentDidMount')
}
shouldComponentUpdate() {
console.log('Child shouldComponentUpdate')
return true
}
componentWillUpdate() {
console.log('Child componentWillUpdate')
}
componentDidUpdate() {
console.log('Child componentDidUpdate')
}
componentWillReceiveProps() {
console.log('Child componentWillReceiveProps')
}
handleClick = () => {
this.setState({utilsMessage: 'Hello JS'})
}
hanldeMouseLeave = () => {
this.setState({utilsMessage: 'Hello Web'})
this.props.onChange('Hello Web')
}
render() {
console.log('Child render')
return (
<div>
<h2 onClick={this.handleClick} onMouseLeave={this.hanldeMouseLeave}>{this.state.utilsMessage}</h2>
<h2>{this.props.homeMessage}</h2>
</div>
)
}
}
export default Utils
父子组件的挂载流程:
修改父组件时父子组件的更新流程:
当父组件状态变化时,不会是否有传递给子组件 props 或者传递给子组件的 props 是否有变化,都会会触发自身和子组件对应的生命周期。
除非使用
React.PureComponent
或者React.memo()
,那样的话,如果没有传递给子组件 props 或者传递给子组件的 props 没有变化,除了子组件的componentWillReceiveProps()
生命周期函数会被触发外,子组件其他的生命周期函数将不会被触发。
修改子组件不影响父组件时的更新流程:
当子组件自身的 state 状态改变,不会对父组件产生副作用的情况下,父组件不会进行更新,也就是不会触发父组件的生命周期。
修改子组件影响父组件时的更新流程:
当子组件自身的 state 状态改变,对父组件产生副作用的情况下,父组件会进行更新,也就是会触发父组件的生命周期。