React的生命周期都懂了嗎?

配圖源自 Freepik

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

一反症、組件的生命周期

組件的生命周期铅碍,主要分為 Mounting(掛載)胞谈、Updating(更新)烦绳、Unmounting(卸載)三個(gè)階段径密。

React ≥ 16.4
Mounting

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

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

Updating

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

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

Unmounting

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

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

當(dāng)渲染過程盆繁,生命周期油昂,或子組件的構(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)該在其他語句之前調(diào)用 super(props)辅辩。否則玫锋,this.props 在構(gòu)造函數(shù)中可能會(huì)出現(xiàn)未定義的 bug撩鹿。

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

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

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

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

需要注意的是墓贿,此方法無法訪問組件實(shí)例,即不能使用 this穴吹。

getDerivedStateFromState 會(huì)在調(diào)用 render 方法之前調(diào)用啥容,并且在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用咪惠。它應(yīng)返回一個(gè) 對(duì)象 來更新 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ài)以響應(yīng) prop 更新(例如篓吁,重置它)杖剪,你可以比較 this.propsnextProps 并在此方法中使用 this.setState() 執(zhí)行 state 轉(zhuǎn)換。

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

在掛載過程彪薛,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() 來初始化 state兵睛。

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

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

2.4 render

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

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

  • React 元素:通常通過 JSX 創(chuàng)建端衰。例如玉锌,<div /> 會(huì)被 React 渲染為 DOM 節(jié)點(diǎn)参淫,<MyComponent /> 會(huì)被 React 渲染為自定義組件邑闺,無論是 <div /> 還是 <MyComponent /> 均為 React 元素。
  • 數(shù)組或 fragments: 使得 render 方法可以返回多個(gè)元素田弥。欲了解更多詳細(xì)信息沈自,請(qǐng)參閱 fragments 文檔榴啸。
  • Portals:可以渲染子節(jié)點(diǎn)到不同的 DOM 子樹中。欲了解更多詳細(xì)信息潜的,請(qǐng)參閱有關(guān) portals 的文檔
  • 字符串或數(shù)值類型:它們在 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 樹中)立即調(diào)用。依賴于 DOM 節(jié)點(diǎn)的初始化應(yīng)該放在這里楷怒。如需通過網(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)致性能問題。通常牛柒,你應(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)化的方式而存在。不要企圖依靠此方法來“阻止”渲染嗡官,因?yàn)檫@可能會(huì)產(chǎn)生 bug箭窜。你應(yīng)該考慮使用內(nèi)置的 PureComponent 組件,而不是手動(dòng)編寫 shouldComponentUpdate()衍腥。PureComponent 會(huì)對(duì) propsstate 進(jìn)行淺層比較磺樱,并減少了跳過必要更新的可能性纳猫。

如果你一定要手寫編寫此函數(shù),可以將 this.propsnextProps 以及 this.statenextState 進(jìn)行比較竹捉,并返回 false 以告知 React 可以跳過更新芜辕。請(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è)條件語句里攻柠,正如上述的例子那樣進(jìn)行處理球订,否則會(huì)導(dǎo)致死循環(huán)。它還會(huì)導(dǎo)致額外的重新渲染瑰钮,雖然用戶不可見冒滩,但會(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ì)再掛載它饭寺。

需要注意的是,我們常說的組件銷毀叫挟,只是組件從頁面中刪除而已艰匙。而并不意味著它真正被銷毀,被 GC 回收抹恳。我們所寫的 JSX 形式的組件员凝,都會(huì)被 Babel 轉(zhuǎn)化成 React.createElement() 形式的JS 對(duì)象(沒錯(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)語法

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

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

3.3 componentDidCatch()

未完待續(xù)...

五跑揉、逐步遷移路徑

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

  • 16.3:為不安全的生命周期引入別名历谍,UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate乖订。(舊的生命周期名稱和新的別名都可以在此版本中使用扮饶。)
  • 未來 16.x 版本:為 componentWillMountcomponentWillReceivePropscomponentWillUpdate 啟用廢棄告警乍构。(舊的生命周期名稱和新的別名都將在這個(gè)版本中工作甜无,但是舊的名稱在開發(fā)模式下會(huì)產(chǎn)生一個(gè)警告。)
  • 17.0:刪除 componentWillMount哥遮、componentWillReceivePropscomponentWillUpdate岂丘。(在此版本之后,只有新的 UNSAFE_ 生命周期名稱可以使用眠饮。)

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

六寨蹋、示例

由于使用新 API(如 getDerivedStateFromPropsgetSnapshotBeforeUpdate)時(shí)扔茅,React 不會(huì)調(diào)用組件的 “unsafe” 生命周期(如 UNSAFE_componentWillMount已旧、UNSAFE_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)擊按鈕通過 setState 更新 count 時(shí)璃诀,會(huì)觸發(fā)以下動(dòng)作:

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

未完待續(xù)...

七弧可、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市文虏,隨后出現(xiàn)的幾起案子侣诺,更是在濱河造成了極大的恐慌殖演,老刑警劉巖氧秘,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異趴久,居然都是意外死亡丸相,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門彼棍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灭忠,“玉大人,你說我怎么就攤上這事座硕〕谧鳎” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵华匾,是天一觀的道長映琳。 經(jīng)常有香客問我,道長蜘拉,這世上最難降的妖魔是什么萨西? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮旭旭,結(jié)果婚禮上谎脯,老公的妹妹穿的比我還像新娘。我一直安慰自己持寄,他們只是感情好源梭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稍味,像睡著了一般废麻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仲闽,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天脑溢,我揣著相機(jī)與錄音,去河邊找鬼。 笑死屑彻,一個(gè)胖子當(dāng)著我的面吹牛验庙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播社牲,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼粪薛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了搏恤?” 一聲冷哼從身側(cè)響起违寿,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熟空,沒想到半個(gè)月后藤巢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡息罗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年掂咒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迈喉。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绍刮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挨摸,到底是詐尸還是另有隱情孩革,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布得运,位于F島的核電站膝蜈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏澈圈。R本人自食惡果不足惜彬檀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞬女。 院中可真熱鬧窍帝,春花似錦、人聲如沸诽偷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽报慕。三九已至深浮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眠冈,已是汗流浹背飞苇。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工菌瘫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人布卡。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓雨让,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忿等。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栖忠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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