1. 程式人生 > >React 學習(四) ---- 生命周期函數

React 學習(四) ---- 生命周期函數

truct upd 進入 console tel 做什麽 區分 寫到 his

  現在我們能修改狀態,頁面可以進行交互了,但是還有一種狀態改變沒有解決,那就是倒計時效果,時間一直在變化,組件狀態也一直在改變,但我們什麽都沒有做,如果要實現這樣的效果,需要怎麽處理? 我們都知道,改變狀態用的是setState, 上次講的加減操作是在把它寫到事件處理函數中來改變狀態的,但現在沒有什麽事件供我們調用,因為我們沒有做任何操作,它卻一直在變化,現在要做的就是找一個機會或入口,來寫setState 函數, 這個機會就是組件的生命周期函數。

  生命周期也是來源於對我們對現實生活的思考, 對於一個有生命的事物來說,它都會經歷從生到死的過程,在這個過程中又分不同的階段,每一個階段都有不同的事情要做。就拿我們自己來說,都會經歷少年,中年,老年三個階段,少年時,我們的重點,就是上學, 中年的重點是工作,老年可能就是幫助我們一下代了。對於組件來說,它是有狀態的,也就是有生命的,就會分為不同的階段,具體分為哪些階段,React 已經給我們定義好了,就是提供了一系列的函數,每一個階段(函數)都有不同的重點,要做什麽事性。這就給我們提供了機會,我們可以做自己的事。 這些函數就叫生命周期函數, 生命周期函數是由React 提供的, 它就負責管理,到了哪個階段,React 會自動調用這些函數, 我們要做的就是在這些函數中,定義要做的事性。這就給我們提供了機會,來改變狀態。

  組件的生命周期,它會經歷三個階段, 裝載階段,更新階段,卸載階段:

    裝載階段: 就是組件初次渲染到頁面上的階段。首次加載頁面時,頁面是空白的,然後經過幾秒時間,頁面中顯示內容。這一過程稱之為裝載。

    更新階段:用戶在頁面上進行交互,組件重新渲染的過程。比如用戶點擊加號, 頁面中的數字由1變成2.

    卸載階段: 組件從頁面中刪除。點擊按鈕顯示一個新的組件,原來的組件就刪除了。

  先來看一下掛載階段,它又具體分為四個階段,進入構造函數,將要掛載,進行渲染,掛載完成。

  進入構造函數:就是每個組件中的構造函數。它的主要作用就是初始化狀態,當然它還有一個props參數,它可以使用組件傳遞過來的props,因此也可以用props進行初始化。

  將要掛載:React 提供了compontentWillMount 函數,不過一般不會在這時做什麽。

  進行渲染:就是我們每一個組件中render 函數,它是至關重要的,因為我們寫一個組件的目的,就是要渲染到頁面上一些東西,它只有返回一個虛擬DOM, ReactDOM 才能根據它構建出真實的DOM渲染到頁面上。它會根據props 和state 渲染出react element, 指導react渲染出真正的dom. 但總存在特殊的情況,組件不需要渲染什麽東西,如驗證用戶有沒有登錄的組件,它確實不需要返什麽,我們只是根據它進行跳轉到不同的頁面,這時直接在render 函數中返回null.

  掛載完成:React 提供了componentDidMounti函數: 當真實的DOM 渲染到頁面上會觸發它。 需要註意的是render 函數在調用完成後,componentDidMount 並不會立刻調用,而是在當所有的render函數都調用完華之後,組件的componentDidMount 才會調用。 寫一下代碼會看得比較清楚,這也是我第一次明白地認識了react的掛載生命周期。

  在講state時,我們寫了一個Counter,現在再寫一個組件CounterWrapper, 它包含Counter 組件,因為此時CounterWrapper 是包含Counter組件的,我們也可以稱CounterWrapper 為父組件,Counter 為子組件,為了更能清楚地明白生命周期函數,我們父組件多包含幾個Counter. 為了對每一個Counter 進行區分,我們給它們傳一個caption 和initValue 參數。 在父組件CounterWrapper 中 由於沒有state, 我只寫了render函數和ComponentDidMount 函數,而在子組件Counter中,它有state, 有完整的生命周期函數,依次寫了四個階段,constructor, componentWillMount, render, componentDidMount, 當然它們只是console.log一下信息,看一下執行的過程。

<script type="text/babel">

// 子組件counter
class Counter extends React.Component {

    constructor(props) {
        super(props);
        console.log(`進入子組件Counter的構造函數constructor ----- ${props.caption}`);
        this.state = {
            count: props.initValue || 0
        }
    }
    // 生命周期函數
    componentWillMount() {
        console.log(`進入子組件Counter的componentWillMount ----- ${this.props.caption}`);
    }
    componentDidMount() {
        console.log(`進入子組件Counter的componentDidMount ----- ${this.props.caption}`);
    }
   render() {
        console.log(`進入子組件Counter的render ----- ${this.props.caption}`);
        const buttonStyle = {
            margin: ‘10px‘
        };
        
    return (
      <div><button style={buttonStyle} >+</button>
        <button style={buttonStyle} value = {5}>-</button>
        <span>{this.props.caption}count: {this.state.count}</span>
      </div>
    );
  }
}

// 父組件CounterWrapper
class CounterWrapper extends React.Component {
    
    render(){
        console.log(‘進入父組件CounterWrapper的render 函數‘);
        return (<div>
            <Counter caption="第一個" initValue={3}></Counter>
            <Counter caption="第二個" initValue={6}></Counter>
            <Counter caption="第三個" initValue={9}></Counter>
        </div>)
    }
    
    componentDidMount() {
        console.log(‘進入父組件CounterWrapper的 componentDidMount 函數‘);
    }
}

   ReactDOM.render(<CounterWrapper />, document.getElementById(‘root‘));
</script>

  控制臺輸出如下信息

技術分享圖片

  

  可以看到它先進入的是父組件CounterWrapper 的render 函數,然後再進入第一個子組件的constructor, componentWillMount和 render, 再進入第二個子組件的constructor, componentWillMount和 render函數,再進入第三個子組件的constructor, componentWillMount和 render函數, 最後才是每一個子組件的componentDidMount, 第二個子組件的ccomponentDidMount函數, 第三個子組件的ccomponentDidMount函數,最後的最後是父組件的CompoentDidMount.

  我們驚奇地發現,組件的掛載生命周期並不是依次調用的,render 函數後面並不是緊緊根隨著componentDidMount 函數。只有當三個組件的render函數都調用完成後,三個組件的componentDidMount 才連在一起被調用。 只有當所有的子組件的componentDidMount都調用了,父組件的componetDidMount 才會被調用。在componentDidMount 函數的調用上,我一直存在誤解,以為render 函數調用後,立即執行它,並有父組件的componentDidMount優於子組件的componentDidMount 執行,真是太想當然了。

  現在再來想一下為什麽會有這樣的設計,當使用componentDidMount 的時候,我們聽得最多的一句話可能就是,在這裏,你可以操作真實的DOM了,換句話說,當 組件compoentDidMount的時候,真實的DOM 已經渲染完畢了,整個頁面都已經渲染完成了。整個頁面就意味著整個DOM樹都已經構建完成了,註意這裏是整個DOM樹。當React進入到組件的render 函數時,它只知道當前組件長什麽樣子,而不會知道整個DOM樹長什麽樣子,所以它只能接著找到第二個組件render, 因為後面有第三個子組件,它還是不知道整個DOM樹長什麽樣子,所以它還要找到第三個組件render, 也就是說它會把所有的組件都循環一遍,直到能夠構建整個DOM樹,它才會渲染真實的DOM, 這也是最為省事的辦法,如果它找到一個渲染一個,後面的組件再對前面的組件有影響,它要把前面的做法再來一次,效率低下。一次渲染就OK了。 渲染之後,肯定是最深層的組件先完成,只有小的完成了才能組成大的,所以最深層的組件的componentDidMount 先執行, 最外層的最後執行。這讓我們想成了JS事件處理階段,當構建DOM樹的過程中,它是捕獲階段,從外面找到最裏面,而在渲染真實的DOM的時候,componentDidMount的時候,它是冒泡階段, 從最裏面到最外面。

  現在再來看更新階段,當掛載階段完成後,用戶看到頁面了。但如果要讓頁面有更好的交互性,那還要能更改狀態或屬性,重新渲染了組件,這就是更新階段了。更新階段又依次分為下面幾個階段:

  1, 將要接收屬性 --> componentWillRecieveProps

  2, 組件應不應該更新 ---> shouldCompoentUpdate

  3, 組件將要更新 ---> componentWillUpdate

  4, 組件進行渲染 --> render

  5, 組件渲染完成 --> componentDidUpdate

React 學習(四) ---- 生命周期函數