[中級(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ù)的作用很簡(jiǎn)單: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,我們簡(jiǎn)要分析一下:

因?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)?;仡櫼陨螹ounting、Updating、Unmounting的生命周期。
  • 遇到舊代碼中的被廢棄的生命周期鉤子時(shí)可查閱相關(guān)使用,并嘗試將其升級(jí)。
  • 使用setState時(shí)需要注意,在原生js事件、setTimeOut等操作中的setState是同步更新狀態(tài)的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容