官术网_书友最值得收藏!

2.3.1 裝載過程

我們先來看裝載過程,當組件第一次被渲染的時候,依次調用的函數是如下這些:

□ constructor

□ getInitialState

□ getDefaultProps

□ componentWillMount

□ render

□ componentDidMount

我們來逐個地詳細解釋這些函數的功能。

1. constructor

我們先來看第一個constructor,也就是ES6中每個類的構造函數,要創造一個組件類的實例,當然會調用對應的構造函數。

要注意,并不是每個組件都需要定義自己的構造函數。在后面的章節我們可以看到,無狀態的React組件往往就不需要定義構造函數,一個React組件需要構造函數,往往是為了下面的目的:

□ 初始化state,因為組件生命周期中任何函數都可能要訪問state,那么整個生命周期中第一個被調用的構造函數自然是初始化state最理想的地方;

□ 綁定成員函數的this環境。

在ES6語法下,類的每個成員函數在執行時的this并不是和類實例自動綁定的。而在構造函數中,this就是當前組件實例,所以,為了方便將來的調用,往往在構造函數中將這個實例的特定函數綁定this為當前實例。

以Counter組件為例,我們的構造函數有這樣如下的代碼:

this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);

這兩條語句的作用,就是通過bind方法讓當前實例中onClickIncrementButton和onClickDecrementButton函數被調用時,this始終是指向當前組件實例。

注意

在某些教程中,大家還會看到另一種bind函數的方式,類似下面的語句:

this.foo = ::this.foo;

等同于下面的語句:

this.foo = this.foo.bind(this);

這里所使用的兩個冒號的::操作符叫做bind操作符,雖然有babel插件支持這種寫法,但是bind操作符可能不會成為ES標準語法的一部分,所以,雖然這種寫法看起來很簡潔,我們在本書中并不使用它。

2. getInitialState和getDefaultProps

getInitialState這個函數的返回值會用來初始化組件的this.state,但是,這個方法只有用React.createClass方法創造的組件類才會發生作用,本書中我們一直使用的ES6語法,所以這個函數根本不會產生作用。

getDefaultProps函數的返回值可以作為props的初始值,和getInitialState一樣,這個函數只在React.createClass方法創造的組件類才會用到。總之,實際上getInitialState和getDefaultProps兩個方法在ES6的方法定義的React組件中根本不會用到。

假如我們用React.createClass方法定義一個組件Sample,設定內部狀態foo的初始值為字符串bar,同時設定一個叫sampleProp的prop初始值為數字值0,代碼如下:

const Sample = React.createClass({
  getInitialState: function() {
    return {foo: 'bar'};
  },
  getDefaultProps: function() {
    return {sampleProp: 0}
  })
});

用ES6的話,在構造函數中通過給this.state賦值完成狀態的初始化,通過給類屬性(注意是類屬性,而不是類的實例對象屬性)defaultProps賦值指定props初始值,達到的效果是完全一樣的,代碼如下:

class Sample extends React.Component {
  constructor(props) {
    super(props);
    this.state = {foo: 'bar'};
  }
});

Sample.defaultProps = {
  return {sampleProp: 0}
};

React.createClass已經被Facebook官方逐漸廢棄,但是在互聯網上還能搜索到很多使用React.createClass的教材,雖然強烈建議不再要使用React.createClass,但是如果讀者你真的要用的話,需要注意關于getInitialState只出現在裝載過程中,也就是說在一個組件的整個生命周期過程中,這個函數只被調用一次,不要在里面放置預期會被多次執行的代碼。

3. render

render函數無疑是React組件中最重要的函數,一個React組件可以忽略其他所有函數都不實現,但是一定要實現render函數,因為所有React組件的父類React.Component類對除render之外的生命周期函數都有默認實現。

通常一個組件要發揮作用,總是要渲染一些東西,render函數并不做實際的渲染動作,它只是返回一個JSX描述的結構,最終由React來操作渲染過程。

當然,某些特殊組件的作用不是渲染界面,或者,組件在某些情況下選擇沒有東西可畫,那就讓render函數返回一個null或者false,等于告訴React,這個組件這次不需要渲染任何DOM元素。

需要注意,render函數應該是一個純函數,完全根據this.state和this.props來決定返回的結果,而且不要產生任何副作用。在render函數中去調用this.setState毫無疑問是錯誤的,因為一個純函數不應該引起狀態的改變。我們在后面的章節會對render函數做詳細的介紹。

4. componentWillMount和componentDidMount

在裝載過程中,componentWillMount會在調用render函數之前被調用,component-DidMount會在調用render函數之后被調用,這兩個函數就像是render函數的前哨和后衛,一前一后,把render函數夾住,正好分別做render前后必要的工作。

不過,我們通常不用定義componentWillMount函數,顧名思義,componentWillMount發生在“將要裝載”的時候,這個時候沒有任何渲染出來的結果,即使調用this.setState修改狀態也不會引發重新繪制,一切都遲了。換句話說,所有可以在這個component-WillMount中做的事情,都可以提前到constructor中間去做,可以認為這個函數存在的主要目的就是為了和componentDidMount對稱。

而componentWillMount的這個兄弟componentDidMount作用就大了。

需要注意的是,render函數被調用完之后,componentDidMount函數并不是會被立刻調用,componentDidMount被調用的時候,render函數返回的東西已經引發了渲染,組件已經被“裝載”到了DOM樹上。

我們還是以ControlPanel為例,在ControlPanel中有三個Counter組件,我們稍微修改Counter的代碼,讓裝載過程中所有生命周期函數都用console.log輸出函數名和caption的值,比如,componentWillMount函數的內容如下:

componentWillMount() {
  console.log('enter componentWillMount ' + this.props.caption);
}

上面修改并沒有添加任何功能,只是通過console.log輸出一些內容,然后我們刷新網頁,在瀏覽器的console里我們能夠看見:

enter constructor: First

enter componentWillMount First

enter render First
enter constructor: Second
enter componentWillMount Second
enter render Second
enter constructor: Third
enter componentWillMount Third
enter render Third
enter componentDidMount First
enter componentDidMount Second
enter componentDidMount Third

可以清楚地看到,雖然componentWillMount都是緊貼著自己組件的render函數之前被調用,componentDidMount可不是緊跟著render函數被調用,當所有三個組件的render函數都被調用之后,三個組件的componentDidMount才連在一起被調用。

之所以會有上面的現象,是因為render函數本身并不往DOM樹上渲染或者裝載內容,它只是返回一個JSX表示的對象,然后由React庫來根據返回對象決定如何渲染。而React庫肯定是要把所有組件返回的結果綜合起來,才能知道該如何產生對應的DOM修改。所以,只有React庫調用三個Counter組件的render函數之后,才有可能完成裝載,這時候才會依次調用各個組件的componentDidMount函數作為裝載過程的收尾。

componentWillMount和componentDidMount這對兄弟函數還有一個區別,就是component-WillMount可以在服務器端被調用,也可以在瀏覽器端被調用;而component-DidMount只能在瀏覽器端被調用,在服務器端使用React的時候不會被調用。

到目前為止,我們構造的React應用例子都只在瀏覽器端使用React,所以看不出區別,但后面第12章關于“同構”應用的介紹時,我們會探討在服務器端使用React的情況。

至于為什么只有componentDidMount僅在瀏覽器端執行,這是一個實現上的決定,而不是設計時刻意為之。不過,如果非要有個解釋的話,可以這么說,既然“裝載”是一個創建組件并放到DOM樹上的過程,那么,真正的“裝載”是不可能在服務器端完成的,因為服務器端渲染并不會產生DOM樹,通過React組件產生的只是一個純粹的字符串而已。

不管怎樣,componentDidMount只在瀏覽器端執行,倒是給了我們開發者一個很好的位置去做只有瀏覽器端才做的邏輯,比如通過AJAX獲取數據來填充組件的內容。

在componentDidMount被調用的時候,組件已經被裝載到DOM樹上了,可以放心獲取渲染出來的任何DOM。

在實際開發過程中,可能會需要讓React和其他UI庫配合使用,比如,因為項目前期已經用jQuery開發了很多功能,需要繼續使用這些基于jQuery的代碼,有時候其他的UI庫做某些功能比React更合適,比如d3.js已經支持了豐富的繪制圖表的功能,在這些情況下,我們不得不考慮如何讓React和其他UI庫和平共處。

以和jQuery配合為例,我們知道,React是用來取代jQuery的,但如果真的要讓React和jQuery配合,就需要是利用componentDidMount函數,當componentDidMount被執行時,React組件對應的DOM已經存在,所有的事件處理函數也已經設置好,這時候就可以調用jQuery的代碼,讓jQuery代碼在已經繪制的DOM基礎上增強新的功能。

在componentDidMount中調用jQuery代碼只處理了裝載過程,要和jQuery完全結合,又要考慮React的更新過程,就需要使用下面要講的componentDidUpdate函數。

主站蜘蛛池模板: 梅河口市| 沙湾县| 榆林市| 文昌市| 曲周县| 星子县| 吴江市| 东山县| 颍上县| 扬州市| 望奎县| 金平| 佛坪县| 资兴市| 进贤县| 桦川县| 周至县| 江油市| 平定县| 忻州市| 乌鲁木齐市| 龙江县| 荃湾区| 穆棱市| 甘洛县| 进贤县| 马山县| 沁源县| 东台市| 长乐市| 大悟县| 酒泉市| 永安市| 光山县| 竹山县| 白玉县| 天津市| 睢宁县| 诏安县| 台山市| 镇坪县|