深入理解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,來顯示(

    hello

    )
  5. state發生變化(

    bye

    )
  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語法也可以用下面的方式生成)

<code>// 傳三個參數:標籤 屬性 內容
// 

item

// 所以其實沒有JSX語法也可以用下面的方式生成 React.createElement('div', {}, 'item')/<code>

虛擬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進行全部的替換,雖然這有可能有一些性能的浪費,但是由於同層比對的算法性能很高,因此又彌補了性能的損耗。


深入理解React底層實現原理

  • 做list循環的時候有一個key值,這樣比對的時候就可以相對應的比對,找出要改變的,以及不需要渲染的,這樣使用key做關聯,極大的提升了虛擬DOM比對的性能,這要保證在新的虛擬DOM後key值不變,這就說明了為什麼做list循環的時候key的值不要是index,因為這樣沒有辦法保證原虛擬DOM和新虛擬DOM的key值一致性,而造成性能的損耗,一般這個key對應後臺數據的唯一id字段,而不是循環的index。
深入理解React底層實現原理

<code>getTodoItem(){
    return this.state.list.map((item,index)=>{
        return (
            
        )
    })
}/<code>

React中ref的使用

  • 在react中使用ref來操作DOM
  • 在react中也可以使用e.target來獲取DOM
  • ref這個參數是一個函數
<code>{this.input = input}}
/>

handleInputChange(e){
// const value = e.target.value; // 原始的方法
const value = this.input.value;
this.setState(() => ({
inputValue: value
}))
}/<code>
  • 一般情況下不推薦使用ref這種方法,因為setState是一個異步函數,因此去操作DOM的時候可能無法正確的輸出頁面的最新DOM情況,有時候比較複雜的操作如動畫之類的,如果一定要使用,就需要在setState的第二個函數,這是個回調函數,在setState完成的時候觸發。
<code>handleBtnClick(e){
    this.setState((prevState)=>({
        list: [...prevState.list, prevState.inputValue], // 展開運算符
        inputValue: '',
    }), ()=>{
        console.log(this.ul.querySelectorAll('div').length);
    });

}/<code>

React中的生命週期函數


深入理解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

本文作者熊冰,個人網站Bing的天涯路),轉載請註明作者和出處,任何問題可在評論區詢問共同進步。


分享到:


相關文章: