[中級(jí)]ReactClass組件:看這一篇就夠了

概覽

我們編寫的大部分 React 的組件都為class組件,掌握class組件的原理和應(yīng)用能幫助我們寫出更好的代碼,本文主要會(huì)從生命周期、state狀態(tài)這兩個(gè)方面來介紹ReactClass組件斯撮,閱讀本文能讓你快速掌握ReactClass組件的要點(diǎn)。

生命周期

我會(huì)先向大家講解react16.3以后的生命周期扶叉,然后解釋下部分老舊的生命周期鉤子被廢棄/不推薦使用的原因勿锅。

執(zhí)行順序

image.png

以上是react官方給出的生命周期圖譜,大致分為3個(gè)階段辜梳,Mounting粱甫、Updating、Unmounting作瞄,下面是每個(gè)階段的執(zhí)行順序茶宵。

Mounting

Mounting階段的執(zhí)行順序依次為:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

Updating

Updating階段的執(zhí)行順序依次為:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

Unmounting

Unmounting階段只會(huì)調(diào)用一個(gè)函數(shù):

  • componentWillUnmount()

Error

除了以上3個(gè)階段,React在錯(cuò)誤發(fā)生時(shí)還提供了兩個(gè)鉤子用來做一些錯(cuò)誤降級(jí)/上報(bào)處理:

  • static getDerivedStateFromError()
  • componentDidCatch()
    以上兩個(gè)方法的區(qū)別在于宗挥,static getDerivedStateFromError()方法用于降級(jí)乌庶,而componentDidCatch()用于一些錯(cuò)誤上報(bào)。

鉤子詳解

下面會(huì)詳細(xì)解釋上面提到的每一個(gè)鉤子

constructor(props)

constructor(props)為class組件的構(gòu)造函數(shù)契耿,一般有兩個(gè)作用:

  • 給state對(duì)象初始化賦值
  • 處理方法瞒大,比如防抖、節(jié)流搪桂、bind等
 constructor(props) {
    super(props)
    this.state = {id: 1}
    this.onSelectUser = throttle(this.onSelectUser, 500)
  }

以上代碼首先給state賦了初始值透敌,然后將onSelectUser方法用節(jié)流函數(shù)處理后得到了一個(gè)新方法盯滚。

static getDerivedStateFromProps(props, state)

該函數(shù)有兩個(gè)要點(diǎn):

  • 每次render前都會(huì)調(diào)用該方法,也就是說無論是初始化階段還是更新階段都會(huì)調(diào)用static getDerivedStateFromProps(props, state)酗电。
  • 該函數(shù)的作用很簡單:return一個(gè)對(duì)象來更新state魄藕,如果return null則不更新任何內(nèi)容。
static getDerivedStateFromProps(props, state) {
    if (props.id !== state.id) {
      return {
        id: props.id
      }
    }
    if (typeof props.expandedkey !== 'undefined') {
      return {
        expandedkey: props.expandedkey
      }
    }
    return null
  }

以上代碼為一個(gè)示例撵术,需要注意的是背率,該方法無法訪問組件實(shí)例,所以你僅僅能根據(jù)props和state的值經(jīng)過一些條件比較嫩与,最后return一個(gè)對(duì)象更新state的值寝姿。

shouldComponentUpdate(nextProps, nextState)

該函數(shù)的唯一作用為return一個(gè)布爾值來決定是否重新渲染組件,true為更新划滋,false為不更新

shouldComponentUpdate(nextProps, nextState) {
    if (this.props.text !== nextProps.text) {
      return true
    }
    if (this.state.weight !== nextState.weight) {
      return true
    }
    return false
  }

以上示例中可以看到饵筑,在shouldComponentUpdate方法中,我們能獲得nextProps古毛、nextState從而與this.props翻翩、this.state進(jìn)行比較,最后決定組件的更新與否稻薇。

render()

該函數(shù)最為常用嫂冻,我們需要知道render函數(shù)究竟怎么渲染為dom節(jié)點(diǎn)。
render函數(shù)會(huì)返回以下類型之一:

  • React元素:<div/>會(huì)被渲染為dom節(jié)點(diǎn)塞椎,<MyComponent/>會(huì)被渲染為自定義組件桨仿,這兩種都為React元素,react根據(jù)第一個(gè)字母的大小寫來決定時(shí)是dom節(jié)點(diǎn)還是自定義組件案狠。
  • 數(shù)組或 fragments
  • Portals
  • 字符串或數(shù)值類型
  • 布爾類型或 null
    在render函數(shù)中服傍,react會(huì)先判斷元素的類型,然后根據(jù)不行的類型執(zhí)行不同的解析方法骂铁。

componentDidMount()

一般人對(duì)該方法都比較熟悉吹零,componentDidMount的調(diào)用時(shí)機(jī)在組件第一次render之后,一般可以在此方法中使用ajax請(qǐng)求獲取數(shù)據(jù)后使用setState再次更新狀態(tài)拉庵。

componentDidMount() {
    this.props.getCode({
      id: 'clue'
    }).then((res) => {
      if(res.code){
       setState({
         code: res.code
       })
      }
    }).catch(err){
      console.log(err)  
    }
  }

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate會(huì)在更新階段的render后被調(diào)用

componentDidUpdate(prevProps) {
    if (prevProps.refresh !== this.props.refresh) {
      this.init()
      this.ajax()
    }
  }

如上示例灿椅,一般在componentDidUpdate中會(huì)對(duì)prevProps、prevState钞支、props茫蛹、state進(jìn)行一些比較,然后執(zhí)行ajax請(qǐng)求或其他函數(shù)烁挟。

getSnapshotBeforeUpdate(prevProps, prevState)

  • 僅在更新階段的render函數(shù)之后被調(diào)用
  • 返回一個(gè)值婴洼,該值會(huì)作為componentDidUpdate方法的第三個(gè)參數(shù)
getSnapshotBeforeUpdate(prevProps, prevState) {
  return this.testNode.scrollHeight
}
componentDidUpdate(prevProps, prevState, snapshot) {
  console.log(snapshot)
}
render(){
  return (
    <div ref = { node => (  this.testNode = node)}> </div>
  )
}

如上示例,在getSnapshotBeforeUpdate方法中適合獲取dom的一些位置屬性然后return撼嗓,并在componentDidUpdate方法中作為第三個(gè)參數(shù)傳入柬采。

componentWillUnmount()

componentWillUnmount() 會(huì)在組件卸載及銷毀之前直接調(diào)用欢唾,一般在此方法中清除一些內(nèi)存,比如清除定時(shí)器警没、中斷網(wǎng)絡(luò)請(qǐng)求等匈辱。

componentWillUnmount(){
    // 清除定時(shí)器
    this.timer = null
}

componentDidCatch(error, info)

該方法的兩個(gè)參數(shù)分別為:

  • error:拋出的錯(cuò)誤
  • info:有關(guān)組件引發(fā)錯(cuò)誤的棧信息
componentDidCatch(error, info) {
    log(info.componentStack);
  }

如上示例,一般在componentDidCatch方法中上報(bào)一些錯(cuò)誤至監(jiān)控平臺(tái)杀迹,有時(shí)也會(huì)在最外層React組件的componentDidCatch方法中對(duì)所有error錯(cuò)誤進(jìn)行捕獲。

static getDerivedStateFromError(error)

該方法會(huì)在后代組件拋出錯(cuò)誤后被調(diào)用,在錯(cuò)誤發(fā)生時(shí),會(huì)返回一個(gè)對(duì)象來更新state耙替,以達(dá)到降級(jí)的目的翼抠。以上兩個(gè)方法的區(qū)別在于,getDerivedStateFromError方法用于降級(jí)邀窃,而componentDidCatch用于一些錯(cuò)誤上報(bào)。

被棄用的鉤子

image.png

以下生命周期都是要被廢除掉的,暫時(shí)可以不用關(guān)注疮茄。

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate
    官方給出的解釋為:這些生命周期方法經(jīng)常被誤解和濫用。

    以componentWillUpdate為例:在異步模式下使用 componentWillUpdate 都是不安全的根暑,想象一下在componentWillUpdate中調(diào)用外部回調(diào)力试,改變外部狀態(tài)以后又可能觸發(fā)props改變,從而又會(huì)調(diào)用componentWillUpdate排嫌,從而陷入死循環(huán)畸裳。而componentDidUpdate方法能保證每次更新只調(diào)用一次,使用上更加安全淳地。

組件狀態(tài)

梳理生命周期能幫助我們熟練書寫React代碼怖糊,而理解組件狀態(tài)如何更新,則幫助我們?cè)趹?yīng)對(duì)疑難問題時(shí)更加輕松颇象。

在React中伍伤,我們使用setState(updater, [callback])更新組件狀態(tài),因?yàn)閟etState并不立刻執(zhí)行遣钳,所以在setState之后我們無法立刻獲得組件的最新state扰魂。

setState({id:1}, () => {
   console.log(this.state.id)
})

如上代碼所示,React提供了一種方式:即在setState的回調(diào)函數(shù)中獲得最新的state(在componentDidUpdate中也能獲取到最新state)耍贾。

既然如此阅爽,setState為什么不做成同步呢,做成異步很重要的一個(gè)原因是:性能優(yōu)化荐开,合并多個(gè)setState付翁,避免多次渲染。

React事件中的setState會(huì)被存放于一個(gè)pending隊(duì)列中晃听,當(dāng)同步代碼都執(zhí)行完后百侧,才會(huì)批量更新state砰识。由于setState的異步執(zhí)行是基于React事件,所以像原生js事件以及setTimeout定時(shí)器中的setState方法就不會(huì)走異步邏輯佣渴,在這些情況下辫狼,我們可以同步獲取最新state。

this.state = {
    id: 1
}
componentDidMount() {
    this.setState({id: this.state.id + 1 });
    console.log(this.state.id);

    this.setState({id: this.state.id + 1 });
    console.log(this.state.id);

    setTimeout(() => {
      console.log(this.state.id);
      this.setState({id: this.state.id + 1 });
      console.log(this.state.id);
      
    }, 0);
  }

以上代碼的執(zhí)行結(jié)果為0 0 1 2辛润,我們簡要分析一下:

因?yàn)閟etState為異步膨处,一開始的兩次setState都進(jìn)入了pending隊(duì)列,所以打印出來都是0砂竖,setTimeout為宏任務(wù)真椿,setTimeout中的state是setState合并執(zhí)行后的結(jié)果,此時(shí)id為1乎澄;而為什么在最后一次setState后打印出了最新的id(2)呢突硝?這是因?yàn)閟etState的異步依賴于React事件,此時(shí)setState在原生js定時(shí)器中執(zhí)行置济,無法實(shí)現(xiàn)異步setState解恰,所以能同步獲取到最新的state。

除了setState以外浙于,React還提供了一個(gè)forceUpdate()函數(shù)來讓組件強(qiáng)制渲染护盈,但是我們?cè)诰帉懘a時(shí)應(yīng)該盡量不使用該方法。

總結(jié)

  • 在日常開發(fā)中路媚,可以經(jīng)郴魄恚回顧以上Mounting、Updating整慎、Unmounting的生命周期脏款。
  • 遇到舊代碼中的被廢棄的生命周期鉤子時(shí)可查閱相關(guān)使用,并嘗試將其升級(jí)裤园。
  • 使用setState時(shí)需要注意撤师,在原生js事件、setTimeOut等操作中的setState是同步更新狀態(tài)的拧揽。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末剃盾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子淤袜,更是在濱河造成了極大的恐慌痒谴,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铡羡,死亡現(xiàn)場離奇詭異积蔚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)烦周,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門尽爆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怎顾,“玉大人,你說我怎么就攤上這事漱贱』蔽恚” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵幅狮,是天一觀的道長募强。 經(jīng)常有香客問我,道長彪笼,這世上最難降的妖魔是什么钻注? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮配猫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杏死。我一直安慰自己泵肄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布淑翼。 她就那樣靜靜地躺著腐巢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玄括。 梳的紋絲不亂的頭發(fā)上冯丙,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音遭京,去河邊找鬼胃惜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哪雕,可吹牛的內(nèi)容都是我干的船殉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼斯嚎,長吁一口氣:“原來是場噩夢啊……” “哼利虫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起堡僻,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤糠惫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钉疫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硼讽,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年陌选,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了理郑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹄溉。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖您炉,靈堂內(nèi)的尸體忽然破棺而出柒爵,到底是詐尸還是另有隱情,我是刑警寧澤赚爵,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布棉胀,位于F島的核電站,受9級(jí)特大地震影響冀膝,放射性物質(zhì)發(fā)生泄漏唁奢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一窝剖、第九天 我趴在偏房一處隱蔽的房頂上張望麻掸。 院中可真熱鬧,春花似錦赐纱、人聲如沸脊奋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诚隙。三九已至,卻和暖如春起胰,著一層夾襖步出監(jiān)牢的瞬間久又,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工效五, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留地消,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓火俄,卻偏偏與公主長得像犯建,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓜客,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344