目錄:
1. 三個階段生命周期函數
2. 不同生命周期詳解
3. 圖解生命周期
組件在頁面上從創建到銷毀分為了不同的狀態,React 給出了讓我們可以調用的生命周期函數。
React 生命周期主要包含 3 個階段,分別是初始化階段、運行中階段、銷毀階段,也就是創建階段、更新階段、卸載階段。在 React 不同的生命周期里會依次觸發不同的鈎子函數。
下面來看 React 生命周期中主要有哪些鈎子函數。
在創建階段會執行 Constructor 構造函數,此時會初始化 props 跟 state 值。在 Constructor 完成之后,會分別執行 componentWillMount、render、componentDidMount 這 3 個鈎子函數。當 React 執行完 componentDidMount 之后,應用被加載到頁面上。之后頁面進行交互操作時,就進入更新階段。
在更新階段會依次觸發 componentWillReceiveProps、shouldComponentUpdate、render、componentDidUpdate,這些函數的觸發是一個循環的過程,每當有交互發生變動的時候,這些函數都會被依次調用。
當將一個組件從頁面上移除的時候,就會觸發 componentWillUnmount 。
下面分階段看生命周期函數都有哪些特性和使用方法。
在創建階段,組件會被創建出來並且被加載到 DOM 中去。
如果嚴格划分創建階段的話,可以划分為初始化階段、掛載階段。
初始化階段是調用 Constructor 的階段,掛載階段就是執行 componentWillMount、render、componentDidMount 的階段。
創建階段 constructor
一個類必須要有一個 constructor 方法,如果這個方法沒有被顯式定義,那么,一個默認的 constructor 函數會被添加。
一般,constructor 方法會返回一個實例對象,返回了實例對象之后我們可以在類函數中使用 this 關鍵字,在 constructor 中需要使用 super 方法調用基類的構造方法,也將父組件的 props 注入給子組件。
在 constructor 中可以做組件的初始化工作,比如說可以初始化 state,在 constructor 中可以直接修改 state,使用 this.state 進行賦值,不能使用 this.setState 方法。
創建階段 componentWillMount
在組件掛載到 DOM 前(UI渲染完成前),componentWillMount 會被調用而且只會被調用一次。此時,如果使用 this.setState 是不會引起重新渲染的。
更多時候,會把組件元素里的內容提前到 constructor 中,所以在項目中很少使用到 componentWillMount 這個生命周期函數。
創建階段 render
render 方法是一個類組件必須有的方法,render 方法會返回一個頂級的 react 元素。render 方法返回的並不是一個真正的 DOM 元素,它渲染的是 Dom Tree 的一個 React 對象,React 之所以效率高,是因為使用了虛擬的 Dom Tree。需要注意的是,不能在 render 方法里去執行 this.setState。
創建階段 componentDidMount
在第一次渲染后調用,當 React 應用執行 componentDidMount 時就證明 UI 渲染完成了,這個生命周期函數只執行一次,它被調用時已經渲染出真實 DOM 了,這個生命周期函數比較適合使用的場景向服務端獲取異步的數據,也就是獲取一些外部數據資源。需要注意的是,當父組件執行 render 的時候,只有當所有的子組件都完成了創建,父組件才會完成渲染,然后執行 componentDidMount。

import React, { PureComponent } from 'react'; import Navbar from "./components/navbar" import ListPage from './components/listPage' class App extends PureComponent { constructor( props ){ super(props) this.state = { listData : [ { id: 1, name: '紅蘋果', price: 2, value: 4 }, { id: 2, name: '青蘋果', price: 3, value: 2 }, ] } console.log('App - constructor'); } componentDidMount () { console.log('App - mounted'); } handleDelete = (id) => { const listData = this.state.listData.filter( item => item.id !== id ) this.setState({ listData }) } handleReset = () => { const _list = this.state.listData.map( item => { const _item = {...item} _item.value = 0 return _item }) this.setState({ listData : _list }) } handleIncrease = (id) => { const _data = this.state.listData.map( item => { if( item.id === id ){ const _item = {...item} _item.value++ return _item }else{ return item } }) this.setState({ listData : _data }) } handleDecrease = (id) => { const _data = this.state.listData.map( item => { if( item.id === id ){ const _item = {...item} _item.value-- if( _item.value < 0 ) _item.value = 0 return _item }else{ return item } }) this.setState({ listData : _data }) } render() { console.log('App - rendering'); return( <> <Navbar onReset = {this.handleReset} total = {this.state.listData.length} /> <ListPage data = {this.state.listData} handleDecrease = {this.handleDecrease} handleIncrease = {this.handleIncrease} handleDelete = {this.handleDelete} /> </> ) } } export default App;
在父組件 App.js 的 constructor 、componentDidMount、render 中加上標記。
在 App.js 的子組件 navbar.jsx 的 render 方法內加上標記 “Nav - rendering”
在 App.js 的子組件 listPage.jsx 的 render 方法內加上標記 “Page - rendering”
在 ListPage.jsx 的子組件 listItem.jsx 的 render 方法內加上標記 “Item - rendering”
控制台:
可以看到整個應用的加載過程,首先是父組件 App.js 執行 constructor,然后執行 render,然后執行 navbar.jsx 跟 listPage.jsx 的 render ,然后執行 listItem.jsx 的 render,當子組件都渲染完之后,父組件 App.js 才執行 componentDidMount,這時完成了整個應用程序的創建。
更新階段 componentWillReceiveProps
組件接收到新 props 的時候調用 componentWillReceiveProps,可以在此對比新 props 和原來的 props,如果 props 發生變化,我們可以進行一些業務邏輯的操作。不過,現在不推薦使用這個方法了,因為有新的生命周期函數可以取代它。
更新階段 shouldComponentUpdate
這個方法可以用於判斷是否要繼續執行 render 方法。它比較新傳入的 nextProps 、 nextState 跟當前的 props、state ,如果返回 true,組件將繼續執行更新,如果返回 false,會阻止組件更新,從而減少不必要的渲染,以達到性能優化的目的。但是,跟 shouldComponentUpdate 相比,更推薦使用 PureComponent 自動實現。 ( shouldComponentUpdate 相關筆記:https://www.cnblogs.com/xiaoxuStudy/p/13350935.html#shouldComponentUpdate )
shouldComponentUpdate 同時接受 props 跟 state,componentWillReceiveProps 只接受 props。
更新階段 componentDidUpdate
componentDidUpdate 在每次 UI 更新時調用。這個方法接收 2 個參數:新傳入的 props 跟 state,可以在這里和原來的數據進行對比,進行一些業務邏輯的處理。可以更新一些外部數據資源。
例 子
在上面例子的基礎上,在 listItem.jsx 上添加一個 componentDidUpdate 函數,在函數內部添加一個判斷,通過 value 對比新傳入的 props 跟之前的 props,如果不一樣的話,輸出 ”Item - Updated“。

import React, { PureComponent } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); class ListItem extends PureComponent { componentDidUpdate(nextProps){ if(nextProps.data.value !== this.props.data.value){ console.log('Item - Updated'); } } render() { console.log('Item - rendering'); return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${this.props.data.value ? '' : '-s'}`}> <button onClick={()=>{this.props.onDecrease(this.props.data.id)}} type="button" className="btn btn-primary" >-</button> <span className={ cls('digital') }>{this.props.data.value}</span> <button onClick={()=>{this.props.onIncrease(this.props.data.id)}} type="button" className="btn btn-primary" >+</button> </div> <div className="col-2 themed-grid-col">¥{this.props.data.price * this.props.data.value}</div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} type="button" className="btn btn-danger btn-sm" >刪除 </button> </div> </div> ); } } export default ListItem;
頁面表現:
清空控制台,然后點擊 “+” 按鈕,可以看到當各個組件完成渲染,整個應用完成更新,最后執行的是 componentDidUpdate,只觸發了一次。根據商品的變化,可以向后端要一些新的數據,這是這個生命周期函數的應用場景。
卸載階段 componentWillUnmount
組件從 DOM 中移除之前立刻被調用 (比如說刪除購物車中的某個商品時) componentWillUnmount,這個生命周期函數可以用來做資源的釋放,比如說可以清理綁定的 timer。
例 子
在 listItem.jsx 中添加 componentWillUnmount 函數,當調用該函數輸出“Item - Deleted”。

import React, { PureComponent } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); class ListItem extends PureComponent { componentDidUpdate(nextProps){ if(nextProps.data.value !== this.props.data.value){ console.log('Item - Updated'); } } componentWillUnmount () { console.log( 'Item - Deleted' ); } render() { console.log('Item - rendering'); return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${this.props.data.value ? '' : '-s'}`}> <button onClick={()=>{this.props.onDecrease(this.props.data.id)}} type="button" className="btn btn-primary" >-</button> <span className={ cls('digital') }>{this.props.data.value}</span> <button onClick={()=>{this.props.onIncrease(this.props.data.id)}} type="button" className="btn btn-primary" >+</button> </div> <div className="col-2 themed-grid-col">¥{this.props.data.price * this.props.data.value}</div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} type="button" className="btn btn-danger btn-sm" >刪除 </button> </div> </div> ); } } export default ListItem;
頁面表現:
清空控制台,點擊“刪除”按鈕后,可以看到當前子組件會在刪除之前立刻調用 componentWillUnmount。
(圖片來源:https://blog.hhking.cn/2018/09/18/react-lifecycle-change/ )
生命周期調用順序(舊)
生命周期調用順序(新)