(十二)React-Native-生命週期詳解
一.React-Native生命週期
說到生命週期,大家大概也能想到就是建立、銷燬、狀態改變。RN的元件就是一個狀態機。它接收兩個輸入引數:props和state,返回一個Virtual DOM。和Native一樣,RN也為我們提供相應的鉤子函式。RN的狀態變化取決於props和state。我們先來看一張經典圖。
這張圖涵蓋了一個元件從建立、執行到銷燬的整個過程。大家可以看到,初始化的時候會呼叫5個函式(按先後順序)。這5個函式在整個元件被建立到銷燬的過程中只調用一次。初始化完畢後,當元件的props或者state改變都會觸發不同的鉤子函式,繼而引發元件的重新渲染。現在我們把這過程拆開一點一點來分析。
初始化
我們先來看初始化,在初始化的過程中,會按順序呼叫下面5個函式。
getDefaultProps:元件例項建立前呼叫,多個例項間共享引用。注意:如果父元件傳遞過來的Props和你在該函式中定義的Props的key一樣,將會被覆蓋。
getInitalState:元件示例建立的時候呼叫的第一個函式。主要用於初始化state。注意:為了在使用中不出現空值,建議初始化state的時候儘可能給每一個可能用到的值都賦一個初始值。
componentWillMount:在render前,getInitalState之後呼叫。僅呼叫一次,可以用於改變state操作。
render:元件渲染函式,會返回一個Virtual DOM,只允許返回一個最外層容器元件。render函式儘量保持純淨,只渲染元件,不修改狀態,不執行副操作(比如計時器)。
componentDidMount:在render渲染之後,React會根據Virtual DOM來生成真實DOM,生成完畢後會呼叫該函式。在瀏覽器端(React),我們可以通過this.getDOMNode()來拿到相應的DOM節點。然而我們在RN中並用不到,在RN中主要在該函式中執行網路請求,定時器開啟等相關操作
下面我們來演示getDefaultProps初始化Props以及父元件覆蓋問題(AppConnect和Provider是和redux相關的程式碼,大家請跳過這一行):
比如我們在這裡定義了SimpleApp的預設Props為一個key為name,value為wsd的字典(ES6以後廢除了getDefaultProps而使用上述方式),然後我們在它的父元件App中傳入一個同樣key為name的Props,然後我們在SimpleApp中使用this.props.name把props打印出來,如下:
可以看到,原先的wsd被後面傳入的kingStart覆蓋了。
然後我們來看初始化State的演示(ES6裡使用constructor):
我們初始化一個state為key為sex,value為boy的state物件,然後我們在componentWillMount函式中改變已經初始化的sex和沒有宣告的age,最後在render中列印:
可以看到我們在render中打印出了state中兩個屬性的值。在這裡我們需要注意的是,如果在componentWillMount中直接修改state的值不會引發render的再次渲染。而如果把修改state的操作放到在render執行完之後的componentDidMount中,是會引發render的再次渲染的。
執行中
初始化完成之後,元件將會進入到執行中狀態,執行中狀態我們將會遇到如下幾個函式:
componentWillReceiveProps(nextProps):props改變(父容器來更改或是redux),將會呼叫該函式。新的props將會作為引數傳遞進來,老的props可以根據this.props來獲取。我們可以在該函式中對state作一些處理。注意:在該函式中更新state不會引起二次渲染。
boolean shouldComponentUpdate(object nextProps, object nextState):該函式傳遞過來兩個引數,新的state和新的props。state和props的改變都會調到該函式。該函式主要對傳遞過來的nextProps和nextState作判斷。如果返回true則重新渲染,如果返回false則不重新渲染。在某些特定條件下,我們可以根據傳遞過來的props和state來選擇更新或者不更新,從而提高效率。
componentWillUpdate(object nextProps, object nextState):與componentWillMount方法類似,元件上會接收到新的props或者state渲染之前,呼叫該方法。但是不可以在該方法中更新state和props。
render:跟初始化的時候功能一樣。
componentDidUpdate(object prevProps,object prevState):和初始化時期的componentDidMount類似,在render之後,真實DOM生成之後呼叫該函式。傳遞過來的是當前的props和state。在該函式中同樣可以使用this.getDOMNode()來拿到相應的DOM節點。如果你需要在執行中執行某些副操作,請在該函式中完成。
我們來演示componentWillReceiveProps的呼叫時機,對於頂層元件,我們新增一個文字及一個點選事件:
按鈕點選以後,我們將自身state的name屬性改變,並傳遞給SimpleApp(這裡的AppConnect就是SimpleApp),結果如下:
我們可以看到,第一次render,列印的是defaultProps傳過來的props。當按鈕點選,頂層元件state改變,引發頂層元件重新渲染,父元件傳遞的name發生改變,componentWillReceiveProps被呼叫,繼而引發二次渲染。在第二次render的時候,打印出來的就是新傳遞過來的props。
銷燬
銷燬階段只有一個函式,很簡單
componentWillUnmount:元件DOM中移除的時候呼叫。在這裡進行一些相關的銷燬操作,比如定時器,監聽等等。
為了加深記憶,我們把初始化和執行中所有的鉤子函式寫出來,讓大家看看最終的執行結果。
我們首先初始化元件,不執行任何操作,列印結果如圖所示:
當我們點選按鈕,改變元件的props之後,列印結果如下:
我們給自身元件添加了一個點選事件,點選之後改變自身的state,如下:
點選之後,再來看呼叫結果:
二.props和state
上面講完了生命週期,我們對props和state的不同點以及相同點作一個總結,加深大家理解。
相同點
1.不管是props還是state的改變,都會引發render的重新渲染。
2.都能由自身元件的相應初始化函式設定初始值。
不同點
1.初始值來源:state的初始值來自於自身的getInitalState(constructor)函式;props來自於父元件或者自身getDefaultProps(若key相同前者可覆蓋後者)。
2.修改方式:state只能在自身元件中setState,不能由父元件修改;props只能由父元件修改,不能在自身元件修改。
3.對子元件:props是一個父元件傳遞給子元件的資料流,這個資料流可以一直傳遞到子孫元件;state代表的是一個元件內部自身的狀態,只能在自身元件中存在。
'use strict' import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, PixelRatio, AlertIOS, ActionSheetIOS, WebView, } from 'react-native'; var Dimensions = require('Dimensions'); var width = Dimensions.get('window').width; var height = Dimensions.get('window').height; class ReactiveOne extends Component{ constructor(props){ super(props); console.log('constructor'); this.state = { sex:'box', }; } componentWillMount() { this.setState({ age: '16', }); console.log('componentWillMount'); } componentDidMount() { console.log('componentDidMount'); this.setState({ sex:'girls', age:'20', }); } render () { console.log('render'); return ( <View style={{backgroundColor:'red',width:width,height:height }}></View> ); } componentWillReceiveProps (nextProps) { console.log('conponentWillReceiveProps',nextProps); } shouldComponentUpdate() { console.log('shouldComponentUpdate'); return true; } componentWillUpdate() { console.log('componentWillUpdate'); } componentDidUpdate () { console.log('componentDidUpdate'); } } AppRegistry.registerComponent('ReactiveOne', ()=>ReactiveOne);
2016-11-04 14:01:38.120 [info][tid:com.facebook.react.JavaScript] constructor
2016-11-04 14:01:38.120 [info][tid:com.facebook.react.JavaScript] componentWillMount
2016-11-04 14:01:38.120 [info][tid:com.facebook.react.JavaScript] render
2016-11-04 14:01:38.124 [info][tid:com.facebook.react.JavaScript] componentDidMount
2016-11-04 14:01:38.125 [info][tid:com.facebook.react.JavaScript] shouldComponentUpdate
2016-11-04 14:01:38.125 [info][tid:com.facebook.react.JavaScript] componentWillUpdate
2016-11-04 14:01:38.126 [info][tid:com.facebook.react.JavaScript] render
2016-11-04 14:01:38.131 [info][tid:com.facebook.react.JavaScript] componentDidUpdate
這裡特別注意: shouldComponentUpdate 函式的返回值是一個bool的 true 表示允許允許應用進行屬性值的修改,執行後續的操作,false 表示不允許執行後續的操作
如果不實現shouldComponentUpdate 該方法,則走系統的預設的去返回true或者false ;
如果使用者實現了shouldComponentUpdate 該方法,則應用走使用者的結果 如果無返回值則報下面的錯誤:
2016-11-04 14:01:00.221 [warn][tid:com.facebook.react.JavaScript] Warning: ReactiveOne.shouldComponentUpdate(): Returned undefined instead of a boolean value. Make sure to return true or false.