索引:
- props, state與render函數關係 – 數據和頁面是如何實現互相聯動的?
- React中的虛擬DOM – React底層是如何實現性能飛躍的?
- 深入瞭解虛擬DOM – 爲什麼React能夠跨端?
- 虛擬DOM的diff算法
- React中ref的操作DOM
- React中的生命週期函數
- 生命週期函數的使用場景
props, state與render函數關係 – 數據和頁面互相聯動的底層機制
當組件的state或者props發生改變的時候,自己的render函數就會重新執行。
注意:當父組件的render被執行的時候,子組件的也render會被重新執行一次(因爲在父組件的render裏面)。
也就是說當綁定的事件改變了state或者props,render函數就會重新執行解析頁面,這個時候解析的時候就會使用新的數據了,所以頁面就會變化。
React中的虛擬DOM
剛纔提到只要state、props改變就會重新render,可以想象要不斷的重新渲染頁面對性能要求非常高,實際上render的性能是非常高的,這是歸功於虛擬DOM。
首先明確DOM的相關操作需要調用web application對性能損耗是比較高的。
先看看常規的思路
- state數據
- JSX模板
- 數據+模板相結合,生成真實的DOM來顯示
- tate改變
- 數據+模板相結合,生成真實的DOM,替換原始的DOM
缺陷在於:第一次生成了一個完整的DOM片段,第二次又生成了一個完整的DOM片段,第二次的DOm替換第一次的DOM,這樣生成、替換非常的消耗性能
改良思路(仍然使用DOM)
- state數據
- JSX模板
- 數據+模板相結合,生成真實的DOM來顯示
- tate改變
- 數據+模板相結合,生成真實的DOM,並不直接替換原始的DOM
- 新的DOM(文檔碎片)原始的DOM作對比,找差異(性能損耗大)
- 找出發生了什麼變化,比如找出了只有input框有差異
- 只用新的DOM中的input元素替換掉老的DOM中的input元素
缺陷在於:性能提升並不明顯,因爲性能消耗在了比對上
React的思路
- state數據
- JSX模板
- 數據+模板相結合,生成虛擬DOM(虛擬DOM就是一個JS數組對象,完整的描述真實的DOM)( [ ‘ idv ‘ , { id : ‘ abc ‘ } , [ ‘ span ‘ , { } , ‘ hello ‘ ] ] ) (用js生成js對象性能損耗極小,生成dom性能損耗大要調用web application)
- 用虛擬DOM的結構,生成真實的DOM,來顯示(<div id=’abc’><span>hello</span></div>)
- state發生變化(<div id=’abc’><span>bye</span></div>)
- 數據+模板生成新的虛擬DOM( [ ‘ idv ‘ , { id : ‘ abc ‘ } , [ ‘ span ‘ , { } , ‘ bye ‘ ] ] )(極大的提升了性能)
- 比較原始虛擬DOM和新的虛擬DOM的區別,找到區別是span中的內容(極大的提升了性能)
- 直接操作DOM改變span中的內容
總結:減少了DOM對真實DOM的創建和對比,而創建和對比的是js對象,從而實現了極大的性能飛躍
深入理解虛擬DOM
Vue和react的虛擬DOM的原理和步驟是完全一致的。
React中真實DOM的生成步驟:JSX -> createElement方法 -> JS對象(虛擬DOM) -> 真實的DOM
因此可見,JSX中的div等標籤僅僅是JSX的語法,並不是DOM,僅用於生成JS對象
其實在React中創建虛擬DOM(js對象)使用的是(沒有JSX語法也可以用下面的方式生成)
// 傳三個參數:標籤 屬性 內容
// <div>item</div>
// 所以其實沒有JSX語法也可以用下面的方式生成
React.createElement('div', {}, 'item')
虛擬DOM的優點:
- 性能提升 DOM比對變成了js的比對
- 它使得跨平臺應用得以實現,React Native(安卓和ios中沒有DOM的概念,使用虛擬DOM(js對象)在所有應用中都可以被使用,然後變成原生客戶端的組件)
虛擬DOM的Diff算法
- Diff算法用於比較原始虛擬DOM和新的虛擬DOM的區別,即兩個js對象該如何比對。
- diff算法全稱叫做difference算法
- setState實際上是異步的,這是爲了提升react底層的性能,是爲了防止時間間隔很短的情況下-多次改變state,React會在這種情況下將幾次改變state合併成一次從而提高性能。
- diff算法是同級比較,假設第一層兩個虛擬DOM節點不一致,就不會往下比了,就會將原始頁面虛擬DOM全部刪除掉,然後用新的虛擬DOM進行全部的替換,雖然這有可能有一些性能的浪費,但是由於同層比對的算法性能很高,因此又彌補了性能的損耗。
- 做list循環的時候有一個key值,這樣比對的時候就可以相對應的比對,找出要改變的,以及不需要渲染的,這樣使用key做關聯,極大的提升了虛擬DOM比對的性能,這要保證在新的虛擬DOM後key值不變,這就說明了爲什麼做list循環的時候key的值不要是index,因爲這樣沒有辦法保證原虛擬DOM和新虛擬DOM的key值一致性,而造成性能的損耗,一般這個key對應後臺數據的唯一id字段,而不是循環的index。
getTodoItem(){
return this.state.list.map((item,index)=>{
return (
<TodoItem
key = {item}
content={item}
index={index}
deleteItem = {this.handleItemDelete}
/>
)
})
}
React中ref的使用
- 在react中使用ref來操作DOM
- 在react中也可以使用e.target來獲取DOM
- ref這個參數是一個函數
<input
id = "insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input)=>{this.input = input}}
/>
handleInputChange(e){
// const value = e.target.value; // 原始的方法
const value = this.input.value;
this.setState(() => ({
inputValue: value
}))
}
- 一般情況下不推薦使用ref這種方法,因爲setState是一個異步函數,因此去操作DOM的時候可能無法正確的輸出頁面的最新DOM情況,有時候比較複雜的操作如動畫之類的,如果一定要使用,就需要在setState的第二個函數,這是個回調函數,在setState完成的時候觸發。
handleBtnClick(e){
this.setState((prevState)=>({
list: [...prevState.list, prevState.inputValue], // 展開運算符
inputValue: '',
}), ()=>{
console.log(this.ul.querySelectorAll('div').length);
});
}
React中的生命週期函數
- 生命週期函數是指在某一個時刻組件會自動調用執行的函數
- reader函數就是一個生命週期函數的例子,當state或props時候改變的時刻救護自動調用執行
- constructor可以理解成一個生命週期函數,在組件被創建的時候就會被執行,但是它是es6語法,不是react特殊的語法
組件掛載的過程
- componentWillMount 在組件即將被掛載到頁面的時刻自動執行,在渲染之前被執行
- render 進行掛載,是必須存在的
- componentDidMount 在組件被掛載到頁面之後自動被執行
- 注意:在state和props改變的時候只有render會執行,componentWillMount和componentDidMount不會執行,他們只會在第一次掛載到頁面的時候被執行
組件更新
- componentWillReceiveProps 兩個條件都要滿足1.當一個組件從父組件接收參數 2. 如果這個組件第一次存在於父組件中不會執行,如果這個組件之前已經存在於父組件中,纔會執行
- shouldComponentUpdate 組件即將被更新之前會執行,如焦點input框的時候,會返回一個true和false來判斷到底要不要更新
- componentWillUpdate 組件被更新之前會自動執行,在shouldComponent返回true之後纔會執行
- componentDidUpdate 組件更新完成之後被執行
組件去除的過程
- componentWillUnmount:但這個組件即將被從頁面中剔除的時候執行
生命週期函數的使用場景
- 防止父組件render的時候子組件也要render,從而提升性能shouldComponentUpdate(nextProps,nextState){ if(nextProps.content !== this.props.content){ return true } return false }
- 頁面初始化的時候在componentDidMount中發送AJAX請求(推薦),或者在constructor中,千萬不要放在render裏面,會造成死循環,也最好不要在componentWillMount中發AJAX,放這裏面是沒有問題的,但是如果在react native中會有問題。
- react沒有內置ajax,使用axios