日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

react如何遍历并比较_[前端进阶] 这可能是最通俗易懂的React 渲染原理及性能优化...

發布時間:2023/12/10 HTML 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 react如何遍历并比较_[前端进阶] 这可能是最通俗易懂的React 渲染原理及性能优化... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

如今的前端,框架橫行,出去面試問到框架是常有的事。

?我比較常用React, 這里就寫了一篇 React 基礎原理的內容, 面試基本上也就問這些, 分享給大家。

React 是什么

React是一個專注于構建用戶界面的 Javascript Library.

一、React做了什么?

  • Virtual Dom模型

  • 生命周期管理

  • setState機制

  • Diff算法

  • React patch、事件系統

  • React的 Virtual Dom模型

virtual dom 實際上是對實際Dom的一個抽象,是一個js對象。react所有的表層操作實際上是在操作Virtual dom。

經過 Diff 算法會計算出 Virtual DOM 的差異,然后將這些差異進行實際的DOM操作更新頁面。

二、React ?總體架構

三、幾點要了解的知識

  • JSX?如何生成Element

  • Element 如何生成DOM

1JSX?如何生成Element

先看一個例子, Counter :

App.js 就做了一件事情,就是把 Counter 組件掛在 #root 上.

Counter 組件里面定義了自己的 state, 這是個默認的 property ,還有一個 handleclick 事件和 一個 render 函數。

看到 render 這個函數里,竟然在 JS 里面寫了 html !?

這是一種 JSX 語法。React 為了方便 View 層組件化,承載了構建 html 結構化頁面的職責。

這里也簡單的舉個例子:

將 html 語法直接加入到 javascript 代碼中,再通過翻譯器轉換到純 javascript 后由瀏覽器執行。

這里調用了 React 和 createElement 方法,這個方法就是用于創建虛擬元素 Virtual Dom 的。

React 把真實的 DOM 樹轉換成 Javascript 對象樹,也就是 Virtual Dom。

每次數據更新后,重新計算 Virtual Dom ,并和上一次生成的 virtual dom 做對比,對發生變化的部分做批量更新。

而 React 是通過創建與更新虛擬元素 Virtual Element 來管理整個Virtual Dom 的。

?虛擬元素可以理解為真實元素的對應,它的構建與更新都是在內存中完成的,并不會真正渲染到 dom 中去。

回到我們的計數器 counter 組件:

注意下 a 標簽 createElement 的返回結果, 這里 CreateElement 只是做了簡單的參數修正,返回一個 ReactElemet 實例對象。

Virtual element 彼此嵌套和混合,就得到了一顆 virtual dom 的樹:

2Element 如何生成DOM

現在我們有了由 ReactElement 組成的 Virtual Dom 樹,接下來我們要怎么我們構建好的 Virtual dom tree 渲染到真正的 DOM 里面呢?

這時可以利用 ReactDOM.render 方法,傳入一個 reactElement 和一個 作為容器的 DOM 節點。

看進去 ReactDOM.render 的源碼,里面有兩個比較關鍵的步驟:

第一步是 instantiateReactComponent。

這個函數創建一個 ReactComponent 的實例并返回,也可以看到 ReactDOM.render 最后返回的也是這個實例。

instantiateReactComponent 方法是初始化組件的入口函數,它通過判斷 node 的類型來區分不同組件的入口。

  • 當 node 為空的時候,初始化空組件。

  • 當 node 為對象,類型 type 字段標記為是字符串,初始化 DOM 標簽。否則初始化自定義組件。

  • 當 node 為字符串或者數字時,初始化文本組件。

  • 雖然 Component 有多種類型,但是它們具有基本的數據結構:ReactComponent 類。

    注意到這里的 setState, 這也是重點之一。

    創建了 Component 實例后,調用 component 的 mountComponent 方法,注意到這里是會被批量 mount 的,這樣組件就開始進入渲染到 DOM 的流程了。

    四、React生命周期

    React 組件基本由三個部分組成,

  • 屬性 props

  • 狀態 state

  • 生命周期方法


  • React 組件可以接受參數props, 也有自身狀態 state。
    一旦接受到的參數 props 或自身狀態 state 有所改變,React 組件就會執行相應的生命周期方法。React 生命周期的全局圖

    首次掛載組件時,按順序執行

  • componentWillMount、

  • render

  • componentDidMount

  • 卸載組件時,執行 componentDidUnmount

    當組件接收到更新狀態,重新渲染組件時,執行

  • componentWillReceiveProps

  • shouldComponentUpdate

  • componentWillUpdate

  • render ?

  • componentDidUpdate

  • 更新策略

    通過 updateComponent 更新組件,首先判讀上下文是否改變,前后元素是否一致,如果不一致且組件的 componentWillReceiveProps 存在,則執行。然后進行 state 的合并。

    調用 shouldComponentUpdate 判斷是否需要進行組件更新,如果存在 componentWillUpdate 則執行。

    后面的流程跟 mountComponent 相似,這里就不贅述了。

    五、setState機制

    為避免篇幅過長,這部分可移步我的專題文章:

    六、Diff算法

    Diff算法用于計算出兩個virtual dom的差異,是React中開銷最大的地方。

    傳統diff算法通過循環遞歸對比差異,算法復雜度為 O(n3)。

    React diff算法制定了三條策略,將算法復雜度從 O(n3)降低到O(n)。

    • 1. UI中的DOM節點跨節點的操作特別少,可以忽略不計。

    • 2. 擁有相同類的組件會擁有相似的DOM結構。擁有不同類的組件會生成不同的DOM結構。

    • 3. 同一層級的子節點,可以根據唯一的ID來區分。

    1. Tree Diff

    對于策略一,React 對樹進行了分層比較,兩棵樹只會對同一層次的節點進行比較。

    只會對相同層級的 DOM 節點進行比較,當發現節點已經不存在時,則該節點及其子節點會被完全刪除,不會用于進一步的比較。

    如果出現了 DOM 節點跨層級的移動操作。

    如上圖這樣,A節點就會被直接銷毀了。

    Diif 的執行情況是:create A -> create C -> create D -> delete A

    2.?Element Diff
  • 當節點處于同一層級時,diff 提供了 3 種節點操作:插入、移動和刪除。

  • 對于同一層的同組子節點添加唯一 key 進行區分。

  • 通過 diff 對比后,發現新舊集合的節點都是相同的節點,因此無需進行節點刪除和創建,只需要將舊集合中節點的位置更新為新集合中節點的位置.

    七、原理解析

    幾個概念

    • 對新集合中的節點進行循環遍歷,新舊集合中是否存在相同節點

    • nextIndex: 新集合中當前節點的位置

    • lastIndex: 訪問過的節點在舊集合中最右的位置(最大位置)

    • If (child._mountIndex < lastIndex)

    對新集合中的節點進行循環遍歷,通過 key 值判斷,新舊集合中是否存在相同節點,如果存在,則進行移動操作。

    在移動操作的過程中,有兩個指針需要注意,

    一個是 nextIndex,表示新集合中當前節點的位置,也就是遍歷新集合時當前節點的坐標。

    另一個是 lastIndex,表示訪問過的節點在舊集合中最右的位置,

    更新流程:

    1

    ( 如果新集合中當前訪問的節點比 lastIndex 大,證明當前訪問節點在舊集合中比上一個節點的位置靠后,則該節點不會影響其他節點的位置,即不進行移動操作。只有當前訪問節點比 lastIndex 小的時候,才需要進行移動操作。)

    首先,我們開遍歷新集合中的節點, 當前 lastIndex = 0, nextIndex = 0,拿到了 B.

    此時在舊集合中也發現了 B,B 在舊集合中的 mountIndex 為 1 , 比當前 lastIndex 0 要大,不滿足 child._mountIndex < lastIndex,對 B 不進行移動操作,更新 lastIndex = 1, 訪問過的節點在舊集合中最右的位置,也就是 B 在舊集合中的位置,nextIndex++ 進入下一步。

    2

    當前 lastIndex = 1, nextIndex = 1,拿到了 A,在舊集合中也發現了 A,A 在舊集合中的 mountIndex 為 0 , 比當前 lastIndex 1 要小,滿足 child._mountIndex < lastIndex,對 A 進行移動操作,此時 lastIndex 依然 = 1, A 的 _mountIndex 更新為 nextIndex = 1, nextIndex++, 進入下一步.

    3

    這里,A 變成了藍色,表示對 A 進行了移動操作。

    當前 lastIndex = 1, nextIndex = 2,拿到了 D,在舊集合中也發現了 D,D 在舊集合中的 mountIndex 為 3 , 比當前 lastIndex 1 要大,不滿足 child._mountIndex < lastIndex,不進行移動操作,此時 lastIndex = 3, D 的 _mountIndex 更新為 nextIndex = 2, nextIndex++, 進入下一步.

    4

    當前 lastIndex = 3, nextIndex = 3,拿到了 C,在舊集合中也發現了 C,C 在舊集合中的 mountIndex 為 2 , 比當前 lastIndex 3 要小,滿足 child._mountIndex < lastIndex,要進行移動,此時 lastIndex不變,為 3, C 的 _mountIndex 更新為 nextIndex = 3.

    5

    由于 C 已經是最后一個節點,因此 diff 操作完成.

    這樣最后,要進行移動操作的只有 A C。

    另一種情況

    剛剛說的例子是新舊集合中都是相同節點但是位置不同。

    那如果新集合中有新加入的節點且舊集合存在需要刪除的節點,

    那 diff 又是怎么進行的呢?比如:

    1

    首先,依舊,我們開遍歷新集合中的節點, 當前 lastIndex = 0, nextIndex = 0,拿到了 B,此時在舊集合中也發現了 B,B 在舊集合中的 mountIndex 為 1 , 比當前 lastIndex 0 要大,不滿足 child._mountIndex < lastIndex,對 B 不進行移動操作,更新 lastIndex = 1, 訪問過的節點在舊集合中最右的位置,也就是 B 在舊集合中的位置,nextIndex++ 進入下一步。

    2

    當前 lastIndex = 1, nextIndex = 1,拿到了 E,發現舊集合中并不存在 E,此時創建新節點 E,nextIndex++,進入下一步

    3

    當前 lastIndex = 1, nextIndex = 2,拿到了 C,在舊集合中也發現了 C,C 在舊集合中的 mountIndex 為 2 , 比當前 lastIndex 1 要大,不滿足 child._mountIndex < lastIndex,不進行移動,此時 lastIndex 更新為 2, nextIndex++ ,進入下一步

    4

    當前 lastIndex = 2, nextIndex = 3,拿到了 A,在舊集合中也發現了 A,A 在舊集合中的 mountIndex 為 0 , 比當前 lastIndex 2 要小,不滿足 child._mountIndex < lastIndex,進行移動,此時 lastIndex 不變, nextIndex++ ,進入下一步

    5

    當完成新集合中所有節點的差異化對比后,還需要對舊集合進行循環遍歷,判斷是否勛在新集合中沒有但舊集合中存在的節點。

    此時發現了 D 滿足這樣的情況,因此刪除 D。

    Diff 操作完成。

    整個過程還是很繁瑣的, 明白過程即可。

    二、性能優化

    由于react中性能主要耗費在于update階段的diff算法,因此性能優化也主要針對diff算法。

    1減少diff算法觸發次數

    減少diff算法觸發次數實際上就是減少update流程的次數。

    正常進入update流程有三種方式:

    1、setState

    setState機制在正常運行時,由于批更新策略,已經降低了update過程的觸發次數。

    因此,setState優化主要在于非批更新階段中(timeout/Promise回調),減少setState的觸發次數。

    常見的業務場景即處理接口回調時,無論數據處理多么復雜,保證最后只調用一次setState。

    2、父組件render

    父組件的render必然會觸發子組件進入update階段(無論props是否更新)。此時最常用的優化方案即為shouldComponentUpdate方法。

    最常見的方式為進行this.props和this.state的淺比較來判斷組件是否需要更新。或者直接使用PureComponent,原理一致。

    需要注意的是,父組件的render函數如果寫的不規范,將會導致上述的策略失效。

    // Bad case
    // 每次父組件觸發render 將導致傳入的handleClick參數都是一個全新的匿名函數引用。
    // 如果this.list 一直都是undefined,每次傳入的默認值[]都是一個全新的Array。
    // hitSlop的屬性值每次render都會生成一個新對象
    class Father extends Component {
    onClick() {}
    render() {
    return <Child handleClick={() => this.onClick()} list={this.list || []} hitSlop={{ top: 10, left: 10}}/>
    }
    }
    // Good case
    // 在構造函數中綁定函數,給變量賦值
    // render中用到的常量提取成模塊變量或靜態成員
    const hitSlop = {top: 10, left: 10};
    class Father extends Component {
    constructor(props) {
    super(props);
    this.onClick = this.onClick.bind(this);
    this.list = [];
    }
    onClick() {}
    render() {
    return <Child handleClick={this.onClick} list={this.list} hitSlop={hitSlop} />
    }
    }

    3forceUpdate

    forceUpdate方法調用后將會直接進入componentWillUpdate階段,無法攔截,因此在實際項目中應該棄用。

    其他優化策略

    ? ?1. ?shouldComponentUpdate

    ? ? ?使用shouldComponentUpdate鉤子,根據具體的業務狀態,減少不必要的props變化導致的渲染。如一個不用于渲染的props導致的update。
    另外, 也要盡量避免在shouldComponentUpdate 中做一些比較復雜的操作, 比如超大數據的pick操作等。

    2. 合理設計state,不需要渲染的state,盡量使用實例成員變量。

    ? ? ?不需要渲染的 props,合理使用 context機制,或公共模塊(比如一個單例服務)變量來替換。

    2正確使用 diff算法
    • 不使用跨層級移動節點的操作。

    • 對于條件渲染多個節點時,盡量采用隱藏等方式切換節點,而不是替換節點。

    • 盡量避免將后面的子節點移動到前面的操作,當節點數量較多時,會產生一定的性能問題。

    看個具體的例子

    這時一個 List 組件,里面有標題,包含 ListItem 子組件的members列表,和一個按鈕,綁定了一個 onclick 事件.

    然后我加了一個插件,可以顯示出各個組件的渲染情況。

    現在我們來點擊改變標題, 看看會發生些什么。

    奇怪的事情發生了,為什么我只改了標題, ?為什么不相關的 ListItem 組件也會重新渲染呢?

    我們可以回到組件生命周期看看為什么。

    還記得這個組件更新的生命周期流程圖嘛,這里的重點在于這個 shouldComponentUpdate。

    只有這個方法返回 true 的時候,才會進行更新組件的操作。我們進步一來看看源碼。

    可以看到這里,原來如果組件沒有定義 shouldComponentUpdate 方法,也是默認認為需要更新的。

    當然,我們的 ListItem 組件是沒有定義這個 shouldComponentUpdate 方法的。

    然后我們使用PureComponent :

    其原理為重新實現了 shouldComponentUpdate 生命周期方法,讓當前傳入的 props 和 state 之前做淺比較,如果返回 false ,那么組件就不會更新了。

    這里也放上一張官網的例圖:

    根據渲染流程,首先會判斷shouldComponentUpdate(SCU)是否需要更新。

    如果需要更新,則調用組件的render生成新的虛擬DOM,然后再與舊的虛擬DOM對比(vDOMEq)。

    如果對比一致就不更新,如果對比不同,則根據最小粒度改變去更新DOM;

    如果SCU不需要更新,則直接保持不變,同時其子元素也保持不變。

    相似的APi還有React.memo:

    回到組件

    再次回到我們的組件中, 這次點擊按鈕, 把第二條數據換掉:

    奇怪的事情發生了,為什么我只改了第二個 listItem, 還是全部 10 個都重新渲染了呢?

    原因在于 shallow compare , 淺比較。

    前面說到,我們不能直接修改 this.state 的值,所以我們把

    this.state.members 拷貝出來再修改第二個人的信息。

    很明顯,因為對象的比較是引用地址,顯然是不相等的。

    因此 shoudComponentUpdate 方法都返回了 false, 組件就進行了更新。

    那么我們怎么能避免這種情況的發生呢?

    其中一個方法是做深比較,但是如果對象或數組層級比較深和復制,那么這個代價就太昂貴了。

    我們就可以用到 Immutable.js 來解決這個問題,進一步提高組件的渲染性能。

    ?Immutable Data 就是一旦被創建,就是不能再更改的數據。

    首先,我們定義了一個 Immutable 的 List 對象,List 對應于原生 JS 的 Array,對 Immutable 對象進行修改、添加或刪除操作,都會返回一個新的 Immutable 對象,所以這里 bbb 不等于 aaa。

    但是同時為了避免深拷貝吧所有節點都復制一遍帶來的性能消耗,Immutable 使用了結構共享,即如果對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其他節點則進行共享。

    結果也是我們預期的那樣。

    性能分析

    用好火焰圖, 該優化的時候再優化。

    八、Hooks ?及其后續更新

    為避免篇幅過長,這部分可移步我的專題文章:

    最后

    總結

    以上是生活随笔為你收集整理的react如何遍历并比较_[前端进阶] 这可能是最通俗易懂的React 渲染原理及性能优化...的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 久久二| 欧美日韩一区二区三区四区五区 | 精品国产一二区 | 亚洲成人av电影 | 成人激情久久 | 翔田千里一区二区三区av | 亚洲国产精品久久久久爰性色 | 日本免费黄色网址 | 龚玥菲三级露全乳视频 | 新婚夫妇白天啪啪自拍 | 亚洲人体视频 | 最近免费中文字幕大全免费版视频 | av在线操 | 亚洲一区在线视频观看 | 天堂在线免费观看 | 香蕉钻洞视频 | 亚洲综合色自拍一区 | 窝窝视频在线观看 | 免费观看nba乐趣影院 | 超碰男人的天堂 | 日韩美女爱爱 | 天堂网av中文字幕 | 国产精品尤物 | 一级h片| 国产综合视频在线观看 | 夜夜干天天操 | 97se综合 | 蜜桃传媒一区二区亚洲 | 正在播放超嫩在线播放 | 国产曰肥老太婆无遮挡 | 久久肉| 日韩福利视频 | 亚洲av成人片无码 | 噼里啪啦动漫 | 国产精品久久久久aaaa | 欧美美女在线 | 欧美黑人一区 | 国产高清毛片 | 亚洲精品xxxxx | 操丰满女人 | 欧州一区| 4388成人网| 免费看的av | 麻豆国产精品一区 | 香蕉在线观看 | 小草av在线 | 人妻精油按摩bd高清中文字幕 | 国产麻豆一精品一av一免费 | 亚洲欧美日本一区二区三区 | 最好看的2018中文2019 | 天堂在线免费观看视频 | 日本天堂免费a | 亚洲一区二区免费在线观看 | 亚洲女人久久久 | 中文字幕免费播放 | 亚洲天堂自拍偷拍 | 在线播放亚洲精品 | 日本在线网址 | 午夜性生活片 | 日本黄色一级视频 | 国产农村妇女精品 | 国产精品区一区二 | 欧美绿帽合集videosex | 欧美日韩精品电影 | 成人做爰69片免费观看 | 日本后进式猛烈xx00动态图 | 久久ww| 日韩中文字幕免费观看 | 日韩一级淫片 | 精品在线视频免费 | 91高跟黑色丝袜呻吟在线观看 | 国产精品二区一区二区aⅴ污介绍 | 欧美性视频网站 | 国产午夜大片 | 亚洲美女屁股眼交3 | 成人久久久精品国产乱码一区二区 | 亚洲吧| 97人妻精品一区二区三区 | 全黄一级裸片视频 | 福利资源在线观看 | 强行无套内谢大学生初次 | 日韩理论片在线观看 | 国产精品麻豆入口 | 亚洲欧美日本国产 | 免费一区二区 | 国产免费又粗又猛又爽 | 亚洲天堂一区二区三区 | 成人在线观看网站 | 福利视频在线导航 | 国产草逼视频 | 国产一区二区三区视频网站 | 亚洲综合伊人 | 国产成人一区二区三区别 | 女性爱爱视频 | 日本久久综合 | 国产按摩一区二区三区 | 69视频免费 | 色妞干网 | 亚洲伊人久久久 |