記憶體維護虛擬 DOM
對於傳統的 DOM 維護,我們的步驟可能是:
1.初始化DOM結構
2.從伺服器獲取新資料
3.使用新資料更新區域性DOM
4.繫結各種事件
首先,我們操作 DOM 是最昂貴的開銷,對於需要反覆更新 DOM 的網頁,無疑是噩夢。而 React 引入了一個全新的概念:虛擬 DOM。虛擬 DOM 是駐留在記憶體裡的一種特殊的物件型別資料,我們可以理解為這是真實 DOM 在記憶體裡的對映。除了結構上的對映外,這個虛擬的 DOM 還包括了渲染真實所需要的資料以及繫結的事件。
最小差異化區域性更新真實 DOM
虛擬 DOM 在建立時,首先是使用 JSX 的語法生成一個真實 DOM 樹的對映,其次是從伺服器端拉取遠端資料,接著注入到這個虛擬DOM 樹中,同時繫結事件。好了,有了虛擬 DOM、資料、事件,萬事俱備。接下來,呼叫 render() 方法一次性渲染出真實的 DOM,然後全量插入到網頁中。虛擬 DOM 靜靜地躺在記憶體裡,等待資料更新。新資料來臨,呼叫 setState() 方法更新資料到虛擬 DOM 中(此過程會進行差異化對比),然後自動呼叫 render() 再區域性更新渲染出真實的 DOM 。
1.一個虛擬DOM,對應一個真實DOM
2. 一旦資料更新,重新生成虛擬DOM,並對真實DOM進行最小差異化區域性更新, 就這麼簡單。卻帶來效能上的較大提升。
JSX語法
React採用jsx語法, jsx是 JavaScript 和 HTML 的結合體, JSX 的目標是在 JavaScript 中更加方便的建立 HTML 節點。JSX 經過直譯器解釋,最終呈現標準的 JavaScript 語法,例如在 React中,下面的 JSX 程式碼:
return ( <div className="commentBox"> Hello, world! I am a CommentBox. </div> );
將被解釋為如下程式碼:
return ( React.createElement( 'div', {className: "commentBox"}, "Hello, world! I am a CommentBox." ) );
可以看到,直譯器會分析 JSX 的語義並將其轉化為建立元素的方法,而並非將 HTML 部分當作簡單的字串原樣輸出,因此不用擔心 JSX會引發 XSS。另一方面,由於 HTML 部分不是被當作字串處理,JSX 中 HTML 元素的部分寫法與標準 HTML 有些許出入。
另外需要注意的是:由於一條 JSX 語句只能夠建立一個虛擬 HTML 節點,因此 JSX 語句中至多擁有一個根 HTML 節點。下面的 JSX 就無法完成解釋:
return ( <h1>Main Title</h1> <h2>Sub Title</h2> )
元件
元件是 React 最基本的渲染單位,React 中全是模組化、可組裝的元件。元件的巢狀和拼裝組成了整個頁面。JSX 語法可以讓元件的構建更加方便,每個元件擁有自己的屬性(props)和狀態(state),通過屬性賦值和狀態修改,React 就可以實現整個頁面的呈現和互動。
React.createElement與document.createElement的區別
我們都知道,JavaScript 通過 DOM 實現與 HTML 的互動,然而我們在 React 中構造一個元素卻呼叫了React.createElement方法,表面上看同樣是生成一個 DOM 物件,那麼為什麼不使用document.createElement方法呢?原因就在於React.createElement方法並沒有真正生成一個 DOM 物件,而是生成了一個虛擬的 DOM 物件,這也是 React 的核心思想所在。React 中的所有操作都是對虛擬 DOM 而不是對真實 DOM 的操作。所以 React 的狀態修改不會實時體現在頁面上,而是在整個元件渲染時,React 會比較元件的狀態改變,僅將發生改變DOM 進行重繪,雖然看起來這個過程十分複雜,但實踐證明這一機制確實能夠提高頁面渲染效率。這正是 React 高效的祕訣之一。
React元件引數說明
當通過呼叫 React.createClass() 來建立元件的時候,你應該提供一個包含render方法的物件,並且也可以包含其它的在這裡描述的生命週期方法。
render
函式原型ReactComponent render(),
render()方法是必須的。當呼叫的時候,會檢測this.props和this.state,返回一個單子級元件。該子級元件可以是虛擬的本地DOM元件(比如 <div /> 或者 React.DOM.div()),也可以是自定義的複合元件。你也可以返回null或者false來表明不需要渲染任何東西。實際上,React渲染一個<noscript>標籤來處理當前的差異檢查邏輯。當返回null或者false的時候this.getDOMNode()將返回null。render()函式應該是純粹的,也就是說該函式不修改元件state,每次呼叫都返回相同的結果,不讀寫DOM資訊,也不和瀏覽器互動(例如通過使用setTimeout)。如果需要和瀏覽器互動,在componentDidMount()中或者其它生命週期方法中做這件事。保持render()純粹,可以使伺服器端渲染更加切實可行,也使元件更容易被理解。
propTypes
函式原型 object propTypes propTypes
是React提供的一種驗證機制,該物件中定義了一系列的驗證方法,可對props
進行驗證。元件初始化時,如果傳遞的props
屬性和propTypes
不匹配,則會列印一個console.warn
日誌。
React.createClass({ propTypes: { // 驗證布林值 optionalBool: React.PropTypes.bool, // 驗證是一個函式 optionalFunc: React.PropTypes.func, // 驗證是數字 optionalNumber: React.PropTypes.number, // 自定義驗證器,驗證失敗需要返回一個 Error 物件。不要直接 // 使用 `console.warn` 或拋異常,因為這樣 `oneOfType` 會失效。 customProp: function(props, propName, componentName) { //自定義的驗證方法 …… } , // 其它驗證 …… }, /* 其它specification... */ }); propTypes使用示例: var App = React.createClass({ propTypes: { site: React.PropTypes.shape({ domain: React.PropTypes.string.isRequired, name: React.PropTypes.string }).isRequired }, render: function() { return ( <p>站點資訊-{this.props.site.name}:{this.props.site.domain}</p> ); } }); var site = { name: 4, // 不合法的型別 domain: 'itbilu.com' } ReactDOM.render( <App site={site} />, document.getElementById('example')); // 執行後會丟擲以下錯誤 // Warning: Failed propType: Invalid prop `site.name` of type `number` supplied to `App`, expected `string`.
mixins
函式原型 array mixins
mixin 陣列允許使用混合來在多個元件之間共享行為。如一個元件需要定期更新,這時我們可以setInterval()
方法很容易的做到,但當不需要它時,我們要取消定時器以節省記憶體。下面我們使用 React 提供的生命週期方法來通知元件建立或銷燬,並結合mixin
,實現元件的定時清理:
var SetIntervalMixin = { componentWillMount: function() { this.intervals = []; }, setInterval: function() { this.intervals.push(setInterval.apply(null, arguments)); }, componentWillUnmount: function() { this.intervals.map(clearInterval); } }); var TickTock = React.createClass({ mixins: [SetIntervalMixin], // 引用 mixin getInitialState: function() { return {seconds: 0}; }, componentDidMount: function() { this.setInterval(this.tick, 1000); // 呼叫 mixin 的方法 }, tick: function() { this.setState({seconds: this.state.seconds + 1}); }, render: function() { return ( <p>React 已經執行了 {this.state.seconds} 秒。</p> ); } }); React.render( <TickTock />, document.getElementById('example'));
statics
函式原型 object statics
statics物件允許你定義靜態的方法,這些靜態的方法可以在元件類上呼叫。例如:
var MyComponent = React.createClass({ statics: { customMethod: function(foo) { return foo === 'bar'; } }, render: function() { } }); MyComponent.customMethod('bar'); // true
在這塊定義的方法都是靜態的,意味著你可以在任何元件例項建立之前呼叫它們,這些方法不能獲取元件的props和state。如果你想在靜態方法中檢查props的值,在呼叫處把props作為引數傳入到靜態方法。
displayName
函式原型 string displayName
displayName 描述外掛的顯示名稱。JSX自動設定該值。
var App = React.createClass({ displayName: 'App', render: function () { return ( <h1>itbilu.com</h1> ) } });
React元件生命週期
所謂生命週期,就是一個物件從開始生成到最後消亡所經歷的狀態某個確定的時間點執行的方法,理解生命週期,是合理開發的關鍵。通過反覆試驗,得到了元件的生命週期在不同狀態下的執行順序:
1.當首次裝載元件時: 按順序執行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount;
2.當重新裝載元件時, 此時按順序執行 getInitialState、componentWillMount、render 和componentDidMount,並不執 行getDefaultProps
3.當再次渲染元件時,元件接受到新的Props,此時按順序執行componentWillReceiveProps、shouldComponentUpdate、
componentWillUpdate、render 和componentDidUpdate。
4.當再次渲染元件時,元件接受到新的state,此時按順序執行shouldComponentUpdate、componentWillUpdate、render 和componentDidUpdate。
4.當元件解除安裝時: 執行componentWillUnmount
如圖,可以把元件生命週期大致分為三個階段:
第一階段:是元件第一次繪製階段,如圖中的上面虛線框內,在這裡完成了元件的載入和初始化;
第二階段:是元件在執行和互動階段,如圖中左下角虛線框,這個階段元件可以處理使用者互動,或者接收事件更新介面;
第三階段:是元件解除安裝消亡的階段,如圖中右下角的虛線框中,這裡做一些元件的清理工作。
下圖對每種情況做了更清晰明瞭的說明
生命週期回撥函式
下面來詳細介紹生命週期中的各回撥函式。
getDefaultProps
其原型如下:
object getDefaultProps();
在元件類建立的時候呼叫一次,然後返回值被快取下來。如果父元件沒有指定 props 中的某個鍵,則此處返回的物件中的相應屬性將會合併到this.props(
使用in
檢測屬性).該方法在任何例項建立之前呼叫,因此不能依賴於this.props
。另外,getDefaultProps()
返回的任何複雜物件將會在例項間共享,而不是每個例項擁有一份拷貝。
getInitialState
其原型如下:
object getInitialState()
在元件掛載之前呼叫一次。返回值將會作為 this.state
的初始值。
componentWillMount
然後,準備載入元件,會呼叫 componentWillMount(),其原型如下:
void componentWillMount()
這個函式呼叫時機是在元件建立,並初始化了狀態之後,在第一次繪製 render() 之前。可以在這裡做一些業務初始化操作,也可以設定元件狀態。這個函式在整個生命週期中只被呼叫一次。
componentDidMount
在元件第一次繪製之後,會呼叫 componentDidMount(),函式原型如下:
void componentDidMount()
通知元件已經載入完成。這個函式呼叫的時候,真實DOM已經構建完成,你可以在這個函式開始獲取其中的元素或者子元件了。需要注意的是,React框架是先呼叫子元件的 componentDidMount(),然後呼叫父元件的componentDidMount函式。從這個函式開始,HTML就可以和JS互動了,例如設定計時setTimeout或者setInterval,或者發起網路請求。這個函式也是隻被呼叫一次。這個函式之後,就進入了穩定執行狀態,等待事件觸發。
componentWillReceiveProps
如果元件收到新的屬性(props),就會呼叫 componentWillReceiveProps(),其原型如下:
void componentWillReceiveProps(object nextProps)
輸入引數 nextProps 是即將被設定的屬性,舊的屬性還是可以通過 this.props 來獲取。在這個回撥函式裡面,你可以根據屬性的變化,通過呼叫 this.setState() 來更新你的元件狀態,這裡呼叫更新狀態是安全的,並不會觸發額外的 render() 呼叫。如下:
componentWillReceiveProps:function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount >this.props.likeCount
});
}
shouldComponentUpdate
當元件接收到新的屬性和狀態改變的話,都會觸發呼叫 shouldComponentUpdate(...),函式原型如下:
boolean shouldComponentUpdate(object nextProps, object nextState)
輸入引數nextProps和上面的componentWillReceiveProps函式一樣,nextState表示元件即將更新的狀態值。這個函式的返回值決定是否需要更新元件,如果true表示需要更新,繼續走後面的更新流程。否者,則不更新,直接進入等待狀態。預設情況下,這個函式永遠返回true用來保證資料變化的時候UI能夠同步更新。在大型專案中,你可以自己過載這個函式,通過檢查變化前後屬性和狀態,來決定UI是否需要更新,能有效提高應用效能。
componentWillUpdate
如果元件狀態或者屬性改變,並且上面的shouldComponentUpdate返回為true,就會開始準更新元件,並呼叫componentWillUpdate(),其函式原型如下:
void componentWillUpdate(object nextProps, object nextState)
輸入引數與shouldComponentUpdate一樣,在這個回撥中,可以做一些在更新介面之前要做的事情。需要特別注意的是,在這個函式裡面,你就不能使用this.setState來修改狀態。這個函式呼叫之後,就會把nextProps和nextState分別設定到this.props和this.state中。緊接著這個函式,就會呼叫render()來更新介面了。
componentDidUpdate
呼叫了render()更新完成介面之後,會呼叫componentDidUpdate()來得到通知,其函式原型如下:
void componentDidUpdate(object prevProps, object prevState)
因為到這裡已經完成了屬性和狀態的更新了,此函式的輸入引數變成了prevProps和prevState。
componentWillUnmount
當元件要被從介面上移除的時候,就會呼叫componentWillUnmount(),其函式原型如下:
void componentWillUnmount()
在這個函式中,可以做一些元件相關的清理工作,例如取消計時器、網路請求等。
總結:
1.react元件的方法和屬性
- 元件的引數
- 渲染元件 :
render
- 除錯輸出 :
displayName
- 跨元件共享 :
mixins
- 驗證物件 :
propTypes
- 靜態方法物件 :
statics
- 渲染元件 :
- 元件生命週期
- 建立期 :
getDefaultProps
- 建立期 :
getInitialState
- 建立期 :
componentWillMount
- 建立期 :
componentDidMount
- 存在期 :
componentWillReceiveProps
- 存在期 :
shouldComponentUpdate
- 存在期 :
componentWillUpdate
- 存在期 :
componentDidUpdate
- 銷燬&清理期 :
componentWillUnmount
- 建立期 :
2.程式設計建議
1. 不建議在 getDefaultProps、getInitialState、shouldComponentUpdate、componentWillUpdate、render,componentWillUnmount
中呼叫setState,特別注意:不能在shouldComponentUpdate 和 componentWillUpdate中呼叫setState,因為更新 state 會導致元件更新進而呼叫shouldComponentUpdate和componentWillUpdate方法,在shouldComponentUpdate和componentWillUpdate中更新了state,又會導致元件更新而呼叫shouldComponentUpdate和componentWillUpdate方法,簡而言之,會導致迴圈呼叫。
2.除了需要操作DOM的初始化操作放在componentDidMount中,其餘的初始化操作應全部丟到componentWillMount中進行,特別是涉及修改state 的操作。因為這些方法如果丟到componentDidMount中,會導致元件載入完成後立刻檢測到state變更,觸發元件再次更新,影響頁面效能。
3.不要在getDefaultProps中進行任何針對例項的操作。該方法在任何例項建立前呼叫,在該方法中訪問不到任何例項。
4.一定要在componentWillUnmount中做好清理工作,否則你會面臨一大堆不可預測且難以除錯的 bug。