React的生命周期都懂了嗎?

配圖源自 Freepik

發(fā)現(xiàn)好像有些沒(méi)有過(guò)的生命周期函數(shù),還沒(méi)完全弄清楚...

一、組件的生命周期

組件的生命周期,主要分為 Mounting(掛載)、Updating(更新)、Unmounting(卸載)三個(gè)階段。

React ≥ 16.4
Mounting

當(dāng)組件示例被創(chuàng)建并插入 DOM 中時(shí),其生命周期調(diào)用順序如下:

以下生命周期方法即將過(guò)時(shí),在新代碼中應(yīng)該避免使用它們UNSAFE_componentWillMount()。

Updating

當(dāng)組件的 props 或 state 發(fā)生變化時(shí)會(huì)觸發(fā)更新。組件更新的生命周期調(diào)用順序如下:

請(qǐng)注意,以下方法即將過(guò)時(shí),在新代碼中應(yīng)該避免使用它們UNSAFE_componentWillUpdate()UNSAFE_componentWillReceiveProps()

Unmounting

當(dāng)組件從 DOM 中移除時(shí)會(huì)調(diào)用如下方法:

Error Handling(錯(cuò)誤處理)

當(dāng)渲染過(guò)程,生命周期,或子組件的構(gòu)造函數(shù)中拋出錯(cuò)誤時(shí),會(huì)調(diào)用如下方法:

二、Mounting(掛載)

2.1 constructor
constructor(props)

如果不初始化 state 或不進(jìn)行方法綁定,則不需要為 React 組件實(shí)現(xiàn)構(gòu)造函數(shù)。

在 React 組件掛載之前,會(huì)調(diào)用它的構(gòu)造函數(shù)。在為 React.Component 子類實(shí)現(xiàn)構(gòu)造函數(shù)時(shí),應(yīng)該在其他語(yǔ)句之前調(diào)用 super(props)。否則,this.props 在構(gòu)造函數(shù)中可能會(huì)出現(xiàn)未定義的 bug。

一般需重寫(xiě)構(gòu)造函數(shù),只做兩件事:

  • 初始化組件內(nèi)部 state,即 this.state = { ... }
  • 為時(shí)間處理函數(shù)綁定實(shí)例,如:this.handleClick = this.handleClick.bind(this)

否則無(wú)需為 React 組件實(shí)現(xiàn)構(gòu)造函數(shù)。

2.2 static getDerivedStateFromProps(不常用)
static getDerivedStateFromProps(props, state)

需要注意的是,此方法無(wú)法訪問(wèn)組件實(shí)例,即不能使用 this

getDerivedStateFromState 會(huì)在調(diào)用 render 方法之前調(diào)用,并且在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用。它應(yīng)返回一個(gè) 對(duì)象 來(lái)更新 state,如果返回 null 則不更新任何內(nèi)容。

componentWillReceiveProps 不同的是,getDerivedStateFromProps 不管原因是什么,都會(huì)在每次渲染前觸發(fā)此方法。而 componentWillReceiveProps 僅在父組件重新渲染時(shí)觸發(fā),而不是在內(nèi)部調(diào)用 setState 時(shí)。

請(qǐng)避免使用派生 state。

2.2.1 UNSAFE_componentWillReceiveProps

此生命周期之前名為 componentWillReceiveProps。該名稱將繼續(xù)使用至 React 17。在 React 16.3 之后,它的替代者是 getDerivedStateFromProps。

UNSAFE_componentWillReceiveProps() 會(huì)在已掛載的組件接收新的 props 之前被調(diào)用。如果你需要更新?tīng)顟B(tài)以響應(yīng) prop 更新(例如,重置它),你可以比較 this.propsnextProps 并在此方法中使用 this.setState() 執(zhí)行 state 轉(zhuǎn)換。

請(qǐng)注意,如果父組件導(dǎo)致組件重新渲染,即使沒(méi)有 props 沒(méi)有更改,也會(huì)調(diào)用此方法。如果只是想處理更改,請(qǐng)確保進(jìn)行當(dāng)前值與變更值的比較。

在掛載過(guò)程,React 不會(huì)針對(duì)初始 props 調(diào)用 UNSAFE_componentWillReceiveProps()。組件只會(huì)在組件的 props 更新時(shí)調(diào)用此方法。調(diào)用 this.setState() 通常不會(huì)觸發(fā) UNSAFE_componentWillReceiveProps()

2.3 UNSAFE_componentWillMount

該生命周期之前名為 componentWillMount,舊名稱仍可使用至 React 17.

UNSAFE_componentWillMount()constructor 之后,render 之前被調(diào)用,因此在此方法中同步調(diào)用 setState() 不會(huì)觸發(fā)額外渲染。通常,我們建議使用 constructor() 來(lái)初始化 state。

避免在此方法引入任何副作用或訂閱,請(qǐng)放置在 componentDidMount()

此方法是服務(wù)端渲染唯一會(huì)調(diào)用的生命周期函數(shù)。

2.4 render

render() 方法是 class 組件中唯一必須實(shí)現(xiàn)的方法。

當(dāng) render 被調(diào)用,它會(huì)檢測(cè) this.propsthis.state 的變化并返回以下類型之一:

  • React 元素:通常通過(guò) JSX 創(chuàng)建。例如,<div /> 會(huì)被 React 渲染為 DOM 節(jié)點(diǎn),<MyComponent /> 會(huì)被 React 渲染為自定義組件,無(wú)論是 <div /> 還是 <MyComponent /> 均為 React 元素。
  • 數(shù)組或 fragments: 使得 render 方法可以返回多個(gè)元素。欲了解更多詳細(xì)信息,請(qǐng)參閱 fragments 文檔。
  • Portals:可以渲染子節(jié)點(diǎn)到不同的 DOM 子樹(shù)中。欲了解更多詳細(xì)信息,請(qǐng)參閱有關(guān) portals 的文檔
  • 字符串或數(shù)值類型:它們?cè)?DOM 中會(huì)被渲染為文本節(jié)點(diǎn)
  • 布爾類型或 null:什么都不渲染。(主要用于支持返回 test && <Child /> 的模式,其中 test 為布爾類型。)

render() 函數(shù)應(yīng)該為純函數(shù),這意味著在不修改組件 state 的情況下,每次調(diào)用時(shí)都返回相同的結(jié)果,并且它不會(huì)直接與瀏覽器交互。

如需與瀏覽器進(jìn)行交互,請(qǐng)?jiān)?componentDidMount() 或其他生命周期方法中執(zhí)行你的操作。保存 render() 為純函數(shù),可以使組件更容易思考。

需要注意都是,shouleComponentUpdate() 返回 false,則不會(huì)調(diào)用 render()。

2.5 componentDidMount
componentDidMount()

componentDidMount() 會(huì)在組件掛載后(插入 DOM 樹(shù)中)立即調(diào)用。依賴于 DOM 節(jié)點(diǎn)的初始化應(yīng)該放在這里。如需通過(guò)網(wǎng)絡(luò)請(qǐng)求獲取數(shù)據(jù),此處是實(shí)例化請(qǐng)求的好地方。

這個(gè)地方是比較合適添加訂閱。如果添加了訂閱,請(qǐng)不要忘記在 componentWillUnmount() 里取消訂閱。

你可以在 componentDidMount() 里直接調(diào)用 setState()。它將會(huì)觸發(fā)額外渲染,但此渲染會(huì)發(fā)生在瀏覽器更新屏幕之前。如此保證了即使在 render() 兩次調(diào)用的情況下,用戶也不會(huì)看到中間狀態(tài)。請(qǐng)謹(jǐn)慎使用該模式,因?yàn)樗鼤?huì)導(dǎo)致性能問(wèn)題。通常,你應(yīng)該在 constructor() 中初始化 state。如果你的渲染依賴于 DOM 節(jié)點(diǎn)的大小或位置,比如實(shí)現(xiàn) modals 或 tooltips 等情況下,你可以使用此方式處理。

三、Updating(更新)

3.1 shouldComponentUpdate(不常用)
shouldComponentUpdate(nextProps, nextState)

根據(jù) shouldComponentUpdate() 的返回值,判斷 React 組件的輸出是否受當(dāng)前 stateprops 更改的影響。默認(rèn)行為是 state 每次發(fā)生變化組件都會(huì)重新渲染。大部分情況下,你應(yīng)該遵循默認(rèn)行為。

當(dāng) propsstate 發(fā)生變化時(shí),shouldComponentUpdate() 會(huì)在渲染執(zhí)行之前被調(diào)用。返回值默認(rèn)為 true。首次渲染或使用 forceUpdate() 時(shí)不會(huì)調(diào)用該方法。

此方法僅作為性能優(yōu)化的方式而存在。不要企圖依靠此方法來(lái)“阻止”渲染,因?yàn)檫@可能會(huì)產(chǎn)生 bug。你應(yīng)該考慮使用內(nèi)置的 PureComponent 組件,而不是手動(dòng)編寫(xiě) shouldComponentUpdate()。PureComponent 會(huì)對(duì) propsstate 進(jìn)行淺層比較,并減少了跳過(guò)必要更新的可能性。

如果你一定要手寫(xiě)編寫(xiě)此函數(shù),可以將 this.propsnextProps 以及 this.statenextState 進(jìn)行比較,并返回 false 以告知 React 可以跳過(guò)更新。請(qǐng)注意,返回 false 并不會(huì)阻止子組件在 state 更改時(shí)重新渲染。

不建議在 shouldComponentUpdate() 中進(jìn)行深層比較或使用 JSON.stringify()。這樣非常影響效率,且會(huì)損害性能。

目前,如果 shouldComponentUpdate() 返回 false,則不會(huì)調(diào)用 UNSAFE_componentWillUpdate()、render()、componentDidUpdate()。后續(xù)版本,React 可能會(huì)將 shouldComponentUpdate 視為提示而不是嚴(yán)格的指令,并且當(dāng)返回 false 時(shí),仍可能導(dǎo)致組件重新渲染。

3.2 UNSAFE_componentWillUpdate(不常用)
UNSAFE_componentWillUpdate(nextProps, nextState)

此生命周期之前名為 componentWillUpdate。該名稱將繼續(xù)使用至 React 17。

當(dāng)組件收到新的 propsstate 時(shí),會(huì)在渲染之前調(diào)用 UNSAFE_componentWillUpdate()。使用此作為更新發(fā)生之前執(zhí)行準(zhǔn)備更新的幾乎。初始渲染不會(huì)調(diào)用此方法。

注意,你不能此方法中調(diào)用 setState();在 UNSAFE_componentWillUpdate() 返回之前,你不應(yīng)該執(zhí)行任何其他操作(例如,dispatch Redux 的 action)觸發(fā)對(duì) React 組件的更新。

通常,此方法可以替換為 componentDidUpdate()。如果你在此方法中讀取 DOM 信息(例如,為了保存滾動(dòng)違章),則可以將此邏輯移至 getSnapShotBeforeUpdate() 中。

如果 shouldComponentUpdate() 返回 false,則不會(huì)調(diào)用 UNSAFE_componentWillUpdate()。

3.3 componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate() 會(huì)在更新后被立即調(diào)用,但首次渲染不會(huì)執(zhí)行此方法。

當(dāng)組件更新后,可以在此處對(duì) DOM 進(jìn)行操作。如果你對(duì)更新前后的 props 進(jìn)行了比較,也可以選擇在此處進(jìn)行網(wǎng)絡(luò)請(qǐng)求。(例如,當(dāng) props 為發(fā)生變化時(shí),則不會(huì)執(zhí)行網(wǎng)絡(luò)請(qǐng)求。)

componentDidUpdate(prevProps) {
  // 典型用法(不要忘記比較 props)
  // 若直接 setState() 會(huì)導(dǎo)致死循環(huán)。
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID)
  }
}

你也可以在 componentDidUpdate() 中直接調(diào)用 setState(),但請(qǐng)注意它必須被包裹在一個(gè)條件語(yǔ)句里,正如上述的例子那樣進(jìn)行處理,否則會(huì)導(dǎo)致死循環(huán)。它還會(huì)導(dǎo)致額外的重新渲染,雖然用戶不可見(jiàn),但會(huì)影響組件性能。不要將 props “鏡像”給 state,請(qǐng)考慮直接使用 props

如果組件實(shí)現(xiàn)了 getSnapshotBeforeUpdate() 生命周期(不常用),則它的返回值將作為 componentDidUpdate() 的第三個(gè)參數(shù) snapshot 傳遞。否則此參數(shù)將為 undefined。

需要注意的是,shouldComponentUpdate() 返回值為 false,則不會(huì)調(diào)用 componentDidUpdate()

四、Unmounting(卸載)

4.1 componentWillUnmount

componentWillUnmount() 會(huì)在組件卸載及銷毀之前直接調(diào)用。在此方法中執(zhí)行必要的清理操作,例如清除 timer,取消網(wǎng)絡(luò)請(qǐng)求或清除 componentDidMount() 中創(chuàng)建的訂閱等。

componentWillUnmount() 中不應(yīng)調(diào)用 setState(),因?yàn)樵摻M件將永遠(yuǎn)不會(huì)重新渲染。組件實(shí)例卸載后,將永遠(yuǎn)不會(huì)再掛載它。

需要注意的是,我們常說(shuō)的組件銷毀,只是組件從頁(yè)面中刪除而已。而并不意味著它真正被銷毀,被 GC 回收。我們所寫(xiě)的 JSX 形式的組件,都會(huì)被 Babel 轉(zhuǎn)化成 React.createElement() 形式的JS 對(duì)象(沒(méi)錯(cuò),React 組件本質(zhì)上就是一個(gè) JavaScript 對(duì)象)。因此只有它不再被引用,垃圾回收機(jī)制才會(huì)回收掉,這才算真正銷毀。這也是為什么要做 componentWillUnmount 清除副作用的原因。

五、其他

5.1 defaultProps

defaultProps 可以為 Class 組件添加默認(rèn) props。這一般用于 props 未賦值,但又不能為 null 的情況。例如:

class CustomButton extends Component {
  // ...
}

CustomButton.defaultProps = {
  type: 'primary'
}

利用 ES6 的 class 靜態(tài)語(yǔ)法

class CustomButton extends Component {
  static defaultProps = {
    type: 'primary'
  }
  // ...
}
5.2 一些問(wèn)題

React 16 之后采用了 Fiber 架構(gòu),只有 componentDidMount 生命周期函數(shù)是確定被執(zhí)行一次的,類似 ComponentWillMount 的生命周期鉤子都有可能執(zhí)行多次,所以不加以在這些生命周期中做有副作用的操作,比如請(qǐng)求數(shù)據(jù)之類。

3.3 componentDidCatch()

未完待續(xù)...

五、逐步遷移路徑

React 遵循語(yǔ)義版本控制,因此這種變化將是逐步的。我們目前的計(jì)劃是:

  • 16.3:為不安全的生命周期引入別名,UNSAFE_componentWillMount、UNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate。(舊的生命周期名稱和新的別名都可以在此版本中使用。)
  • 未來(lái) 16.x 版本:為 componentWillMountcomponentWillReceivePropscomponentWillUpdate 啟用廢棄告警。(舊的生命周期名稱和新的別名都將在這個(gè)版本中工作,但是舊的名稱在開(kāi)發(fā)模式下會(huì)產(chǎn)生一個(gè)警告。)
  • 17.0:刪除 componentWillMount、componentWillReceivePropscomponentWillUpdate。(在此版本之后,只有新的 UNSAFE_ 生命周期名稱可以使用。)

這里的 “unsafe” 不是指安全性,而是表示使用這些生命周期的代碼在 React 的未來(lái)版本中更有可能出現(xiàn) bug,尤其是在啟用異步渲染之后。

六、示例

由于使用新 API(如 getDerivedStateFromProps、getSnapshotBeforeUpdate)時(shí),React 不會(huì)調(diào)用組件的 “unsafe” 生命周期(如 UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate),因此暫時(shí)先注釋掉新 API。同時(shí)使用,會(huì)出現(xiàn)類似如下警告??:

Warning: Unsafe legacy lifecycles will not be called for components using new component APIs.

LifeCycle uses getDerivedStateFromProps() but also contains the following legacy lifecycles:
  UNSAFE_componentWillMount

The above lifecycles should be removed. Learn more about this warning here:
https://fb.me/react-unsafe-component-lifecycles

示例基于 React 16.12.0 版本。

import React, { Component } from 'react'

export default class LifeCycle extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
    this.setCount = this.setCount.bind(this)
    console.log('---> constructor')
  }

  static getDerivedStateFromProps(prevProps, prevState) {
    console.log('---> getDerivedStateFromProps')
    return null
  }

  // 不會(huì)為使用新組件 API 的組件調(diào)用不安全的遺留生命周期。
  // Warning: Unsafe legacy lifecycles will not be called for components using new component APIs.
  // LifeCycle uses getDerivedStateFromProps() but also contains the following legacy lifecycles: UNSAFE_componentWillMount
  // UNSAFE_componentWillMount() {
  //   console.log('---> UNSAFE_component')
  // }

  componentDidMount() {
    console.log('---> componentDidMount')
  }

  setCount() {
    this.setState({ count: this.state.count + 1 })
  }

  render() {
    console.log('---> render')

    return (
      <div>
        <div>Count: {this.state.count}</div>
        <button onClick={this.setCount}>Add</button>
      </div>
    )
  }
}

當(dāng)首次加載加載時(shí),分別觸發(fā)了以下生命周期,打印結(jié)果:

---> constructor
---> UNSAFE_componentWillMount
---> render
---> componentDidMount

當(dāng)我們點(diǎn)擊按鈕通過(guò) setState 更新 count 時(shí),會(huì)觸發(fā)以下動(dòng)作:

---> shouldComponentUpdate
---> UNSAFE_componentWillUpdate
---> render
---> componentDidUpdate

未完待續(xù)...

七、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容