深入理解React底層實現原理

索引:

  1. props, state與render函數關係 – 數據和頁面是如何實現互相聯動的?
  2. React中的虛擬DOM – React底層是如何實現性能飛躍的?
  3. 深入瞭解虛擬DOM – 爲什麼React能夠跨端?
  4. 虛擬DOM的diff算法
  5. React中ref的操作DOM
  6. React中的生命週期函數
  7. 生命週期函數的使用場景

props, state與render函數關係 – 數據和頁面互相聯動的底層機制

當組件的state或者props發生改變的時候,自己的render函數就會重新執行。

注意:當父組件的render被執行的時候,子組件的也render會被重新執行一次(因爲在父組件的render裏面)。

也就是說當綁定的事件改變了state或者props,render函數就會重新執行解析頁面,這個時候解析的時候就會使用新的數據了,所以頁面就會變化。

React中的虛擬DOM

剛纔提到只要state、props改變就會重新render,可以想象要不斷的重新渲染頁面對性能要求非常高,實際上render的性能是非常高的,這是歸功於虛擬DOM。

首先明確DOM的相關操作需要調用web application對性能損耗是比較高的。

先看看常規的思路

  1. state數據
  2. JSX模板
  3. 數據+模板相結合,生成真實的DOM來顯示
  4. tate改變
  5. 數據+模板相結合,生成真實的DOM,替換原始的DOM
    缺陷在於:第一次生成了一個完整的DOM片段,第二次又生成了一個完整的DOM片段,第二次的DOm替換第一次的DOM,這樣生成、替換非常的消耗性能

改良思路(仍然使用DOM)

  1. state數據
  2. JSX模板
  3. 數據+模板相結合,生成真實的DOM來顯示
  4. tate改變
  5. 數據+模板相結合,生成真實的DOM,並不直接替換原始的DOM
  6. 新的DOM(文檔碎片)原始的DOM作對比,找差異(性能損耗大)
  7. 找出發生了什麼變化,比如找出了只有input框有差異
  8. 只用新的DOM中的input元素替換掉老的DOM中的input元素
    缺陷在於:性能提升並不明顯,因爲性能消耗在了比對上

React的思路

  1. state數據
  2. JSX模板
  3. 數據+模板相結合,生成虛擬DOM(虛擬DOM就是一個JS數組對象,完整的描述真實的DOM)( [ ‘ idv ‘ , { id : ‘ abc ‘ } , [ ‘ span ‘ , { } , ‘ hello ‘ ] ] ) (用js生成js對象性能損耗極小,生成dom性能損耗大要調用web application)
  4. 用虛擬DOM的結構,生成真實的DOM,來顯示(<div id=’abc’><span>hello</span></div>)
  5. state發生變化(<div id=’abc’><span>bye</span></div>)
  6. 數據+模板生成新的虛擬DOM( [ ‘ idv ‘ , { id : ‘ abc ‘ } , [ ‘ span ‘ , { } , ‘ bye ‘ ] ] )(極大的提升了性能)
  7. 比較原始虛擬DOM和新的虛擬DOM的區別,找到區別是span中的內容(極大的提升了性能)
  8. 直接操作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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章