1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        從 setState 聊到 React 性能優(yōu)化

        共 6057字,需瀏覽 13分鐘

         ·

        2021-09-11 10:22

        點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)

        回復(fù)加群,加入前端Q技術(shù)交流群



          作者:風(fēng)不識(shí)途


        https://segmentfault.com/a/1190000039776687

        setState的同步和異步

        1.為什么使用setState

        • 開發(fā)中我們并不能直接通過修改 state 的值來讓界面發(fā)生更新
          • 因?yàn)槲覀冃薷牧?nbsp;state 之后, 希望 React 根據(jù)最新的 Stete 來重新渲染界面, 但是這種方式的修改 React 并不知道數(shù)據(jù)發(fā)生了變化
          • React 并沒有實(shí)現(xiàn)類似于 Vue2 中的 Object.defineProperty 或者 Vue3 中的Proxy的方式來監(jiān)聽數(shù)據(jù)的變化
          • 我們必須通過 setState 來告知 React 數(shù)據(jù)已經(jīng)發(fā)生了變化
        • 疑惑: 在組件中并沒有實(shí)現(xiàn) steState 方法, 為什么可以調(diào)用呢?
          • 原因很簡(jiǎn)單: setState方法是從 Component 中繼承過來的

        2.setState異步更新

        setState是異步更新的

        • 為什么setState設(shè)計(jì)為異步呢?
          • setState 設(shè)計(jì)為異步其實(shí)之前在 GitHub 上也有很多的討論
          • React核心成員(Redux的作者)Dan Abramov也有對(duì)應(yīng)的回復(fù), 有興趣的可以看一下
        • 簡(jiǎn)單的總結(jié): setState設(shè)計(jì)為異步, 可以顯著的提高性能
          • 如果每次調(diào)用 setState 都進(jìn)行一次更新, 那么意味著 render 函數(shù)會(huì)被頻繁的調(diào)用界面重新渲染, 這樣的效率是很低的
          • 最好的方法是獲取到多個(gè)更新, 之后進(jìn)行批量更新
        • 如果同步更新了 state, 但還沒有執(zhí)行 render 函數(shù), 那么stateprops不能保持同步
          • stateprops不能保持一致性, 會(huì)在開發(fā)中產(chǎn)生很多的問題

        3.如何獲取異步的結(jié)果

        • 如何獲取 setState 異步更新state后的值?
        • 方式一: setState的回調(diào)
          • setState接收兩個(gè)參數(shù): 第二個(gè)參數(shù)是回調(diào)函數(shù)(callback), 這個(gè)回調(diào)函數(shù)會(huì)在state更新后執(zhí)行
        • 方式二: componentDidUpdate生命周期函數(shù)

        3.setState一定是異步的嗎?

        • 其實(shí)可以分成兩種情況
        • 在組件生命周期或React合成事件中, setState是異步的
        • setTimeou或原生DOM事件中, setState是同步的
        • 驗(yàn)證一: 在setTimeout中的更新 —> 同步更新
        • 驗(yàn)證二: 在原生DOM事件 —> 同步更新

        4.源碼分析

        setState的合并

        1.數(shù)據(jù)的合并

        • 通過setState去修改message,是不會(huì)對(duì)其他 state 中的數(shù)據(jù)產(chǎn)生影響的
          • 源碼中其實(shí)是有對(duì) 原對(duì)象 和 新對(duì)象 進(jìn)行合并的

        2.多個(gè)state的合并

        • 當(dāng)我們的多次調(diào)用了 setState只會(huì)生效最后一次state
        • setState合并時(shí)進(jìn)行累加: 給setState傳遞函數(shù), 使用前一次state中的值

        React 更新機(jī)制

        1.React 更新機(jī)制

        • 我們?cè)谇懊嬉呀?jīng)學(xué)習(xí)React的渲染流程:
        • 那么 React 的更新流程呢?
        • React基本流程

        2.React 更新流程

        • React在 props 或 state 發(fā)生改變時(shí),會(huì)調(diào)用 React 的 render 方法,會(huì)創(chuàng)建一顆不同的樹

        • React需要基于這兩顆不同的樹之間的差別來判斷如何有效的更新UI

        • 如果一棵樹參考另外一棵樹進(jìn)行完全比較更新, 那么即使是最先進(jìn)的算法, 該算法的復(fù)雜程度為 O(n 3 ^3 3),其中 n 是樹中元素的數(shù)量

          • 如果在 React 中使用了該算法, 那么展示 1000 個(gè)元素所需要執(zhí)行的計(jì)算量將在十億的量級(jí)范圍
        • 這個(gè)開銷太過昂貴了, React的更新性能會(huì)變得非常低效

        • 于是,React對(duì)這個(gè)算法進(jìn)行了優(yōu)化,將其優(yōu)化成了O(n),如何優(yōu)化的呢?

          • 同層節(jié)點(diǎn)之間相互比較,不會(huì)跨節(jié)點(diǎn)比較

          • 不同類型的節(jié)點(diǎn),產(chǎn)生不同的樹結(jié)構(gòu)

          • 開發(fā)中,可以通過key來指定哪些節(jié)點(diǎn)在不同的渲染下保持穩(wěn)定

        情況一: 對(duì)比不同類型的元素

        • 當(dāng)節(jié)點(diǎn)為不同的元素,React會(huì)拆卸原有的樹并且建立起新的樹

          • 當(dāng)一個(gè)元素從 <a> 變成 <img>,從 <Article> 變成 <Comment>,或從 <button> 變成 <div> 都會(huì)觸發(fā)一個(gè)完整的重建流程

          • 當(dāng)卸載一棵樹時(shí),對(duì)應(yīng)的DOM節(jié)點(diǎn)也會(huì)被銷毀,組件實(shí)例將執(zhí)行 componentWillUnmount() 方法

          • 當(dāng)建立一棵新的樹時(shí),對(duì)應(yīng)的 DOM 節(jié)點(diǎn)會(huì)被創(chuàng)建以及插入到 DOM 中,組件實(shí)例將執(zhí)行 componentWillMount() 方法,緊接著 componentDidMount() 方法

        • 比如下面的代碼更改:

          • React 會(huì)銷毀 Counter 組件并且重新裝載一個(gè)新的組件,而不會(huì)對(duì)Counter進(jìn)行復(fù)用

        情況二: 對(duì)比同一類型的元素

        • 當(dāng)比對(duì)兩個(gè)相同類型的 React 元素時(shí),React 會(huì)保留 DOM 節(jié)點(diǎn)僅對(duì)比更新有改變的屬性
        • 比如下面的代碼更改:
          • 通過比對(duì)這兩個(gè)元素,React知道只需要修改 DOM 元素上的 className 屬性
        • 比如下面的代碼更改:

          • 當(dāng)更新 style 屬性時(shí),React 僅更新有所改變的屬性。

          • 通過比對(duì)這兩個(gè)元素,React 知道只需要修改 DOM 元素上的 color 樣式,無需修改 fontWeight

        • 如果是同類型的組件元素:

          • 組件會(huì)保持不變,React會(huì)更新該組件的props,并且調(diào)用componentWillReceiveProps() 和 componentWillUpdate()方法

          • 下一步,調(diào)用 render() 方法,diff 算法將在之前的結(jié)果以及新的結(jié)果中進(jìn)行遞歸

        情況三: 對(duì)子節(jié)點(diǎn)進(jìn)行遞歸

        • 在默認(rèn)條件下,當(dāng)遞歸 DOM 節(jié)點(diǎn)的子元素時(shí),React 會(huì)同時(shí)遍歷兩個(gè)子元素的列表;當(dāng)產(chǎn)生差異時(shí),生成一個(gè) mutation

          • 我們來看一下在最后插入一條數(shù)據(jù)的情況:??

          • 前面兩個(gè)比較是完全相同的,所以不會(huì)產(chǎn)生mutation

          • 最后一個(gè)比較,產(chǎn)生一個(gè)mutation,將其插入到新的DOM樹中即可

        • 但是如果我們是在前面插入一條數(shù)據(jù):

          • React會(huì)對(duì)每一個(gè)子元素產(chǎn)生一個(gè)mutation,而不是保持 <li>星際穿越</li> 和 <li>盜夢(mèng)空間</li>的不變
          • 這種低效的比較方式會(huì)帶來一定的性能問題

        React 性能優(yōu)化

        1.key的優(yōu)化

        • 我們?cè)谇懊姹闅v列表時(shí),總是會(huì)提示一個(gè)警告,讓我們加入一個(gè)key屬性:
        • 方式一:在最后位置插入數(shù)據(jù)

          • 這種情況,有無key意義并不大
        • 方式二:在前面插入數(shù)據(jù)

          • 這種做法,在沒有 key 的情況下,所有的<li>都需要進(jìn)行修改
        • 在下面案例: 當(dāng)子元素 (這里的li元素) 擁有 key 時(shí)

          • React 使用 key 來匹配原有樹上的子元素以及最新樹上的子元素

          • 下面這種場(chǎng)景下, key為 111 和 222 的元素僅僅進(jìn)行位移,不需要進(jìn)行任何的修改

          • key為 333 的元素插入到最前面的位置即可

        key的注意事項(xiàng):

        • key應(yīng)該是唯一的
        • key不要使用隨機(jī)數(shù)(隨機(jī)數(shù)在下一次render時(shí),會(huì)重新生成一個(gè)數(shù)字)
        • 使用index作為key,對(duì)性能是沒有優(yōu)化的

        2.render函數(shù)被調(diào)用

        • 我們使用之前的一個(gè)嵌套案例:

          • 在App中,我們?cè)黾恿艘粋€(gè)計(jì)數(shù)器的代碼
        • 當(dāng)點(diǎn)擊 +1 時(shí),會(huì)重新調(diào)用 App 的 render 函數(shù)

          • 而當(dāng) App 的 render函數(shù)被調(diào)用時(shí),所有的子組件的 render 函數(shù)都會(huì)被重新調(diào)用
        • 那么,我們可以思考一下,在以后的開發(fā)中,我們只要是修改 了App中的數(shù)據(jù),所有的子組件都需要重新render,進(jìn)行 diff算法,性能必然是很低的:
          • 事實(shí)上,很多的組件沒有必須要重新render
          • 它們調(diào)用 render 應(yīng)該有一個(gè)前提,就是依賴的數(shù)據(jù)(state、 props) 發(fā)生改變時(shí),再調(diào)用自己的render方法
        • 如何來控制 render 方法是否被調(diào)用呢?
          • 通過shouldComponentUpdate方法即可

        3.shouldComponentUpdate

        React給我們提供了一個(gè)生命周期方法 shouldComponentUpdate(很多時(shí)候,我們簡(jiǎn)稱為SCU),這個(gè)方法接受參數(shù),并且需要有返回值;主要作用是:**控制當(dāng)前類組件對(duì)象是否調(diào)用render**方法

        • 該方法有兩個(gè)參數(shù):
        • 參數(shù)一: nextProps修改之后, 最新的 porps屬性
        • 參數(shù)二: nextState 修改之后, 最新的 state 屬性
        • 該方法返回值是一個(gè) booolan 類型
        • 返回值為true, 那么就需要調(diào)用 render 方法
        • 返回值為false, 那么不需要調(diào)用 render 方法
        • 比如我們?cè)贏pp中增加一個(gè)message屬性:
        • JSX中并沒有依賴這個(gè)message, 那么它的改變不應(yīng)該引起重新渲染
        • 但是通過setState修改 state 中的值, 所以最后 render 方法還是被重新調(diào)用了
        // 決定當(dāng)前類組件對(duì)象是否調(diào)用render方法
        // 參數(shù)一: 最新的props
        // 參數(shù)二: 最新的state
        shouldComponentUpdate(nextProps, nextState) {
          // 默認(rèn)是: return true
          // 不需要在頁面上渲染則不調(diào)用render函數(shù)
          return false
        }

        4.PureComponent

        • 如果所有的類, 我們都需要手動(dòng)來實(shí)現(xiàn) shouldComponentUpdate, 那么會(huì)給我們開發(fā)者增加非常多的工作量
          • 我們?cè)O(shè)想一下在shouldComponentUpdate中的各種判斷目的是什么?
          • props 或者 state 中數(shù)據(jù)是否發(fā)生了改變, 來決定shouldComponentUpdate返回 true 或 false
        • 事實(shí)上 React 已經(jīng)考慮到了這一點(diǎn), 所以 React 已經(jīng)默認(rèn)幫我們實(shí)現(xiàn)好了, 如何實(shí)現(xiàn)呢?
          • 將 class 繼承自 PureComponent
          • 內(nèi)部會(huì)進(jìn)行淺層對(duì)比最新的 state 和 porps , 如果組件內(nèi)沒有依賴 porpsstate 將不會(huì)調(diào)用render
          • 解決的問題: 比如某些子組件沒有依賴父組件的stateprops, 但卻調(diào)用了render函數(shù)

        5.shallowEqual方法

        這個(gè)方法中,調(diào)用 !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState),這個(gè) shallowEqual就是進(jìn)行淺層比較:

        6.高階組件memo

        • 函數(shù)式組件如何解決render: 在沒有依賴 state 或 props 但卻重新渲染 render 問題

          • 我們需要使用一個(gè)高階組件memo

          • 我們將之前的Header、Banner、ProductList都通過 memo 函數(shù)進(jìn)行一層包裹

          • Footer沒有使用 memo 函數(shù)進(jìn)行包裹;

          • 最終的效果是,當(dāng)counter發(fā)生改變時(shí),Header、Banner、ProductList的函數(shù)不會(huì)重新執(zhí)行,而 Footer 的函數(shù)會(huì)被重新執(zhí)行

        import React, { PureComponent, memo } from 'react'

        // MemoHeader: 沒有依賴props,不會(huì)被重新調(diào)用render渲染
        const MemoHeader = memo(function Header({
          console.log('Header被調(diào)用')
          return <h2>我是Header組件</h2>
        })


        聲明:文章著作權(quán)歸作者所有,如有侵權(quán),請(qǐng)聯(lián)系小編刪除。



        往期推薦


        大廠面試過程復(fù)盤(微信/阿里/頭條,附答案篇)
        面試題:說說事件循環(huán)機(jī)制(滿分答案來了)
        專心工作只想搞錢的前端女程序員的2020



        最后


        • 歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

        • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

        點(diǎn)個(gè)在看支持我吧
        瀏覽 57
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            五月天操逼图 | 国产精品99久久久久久一二区 | 免费看h片网站 | 激情视频网址 | 99香蕉国产精品偷在线观看 | 色婷婷国产在线视频 | 午夜激情福利 | 91豆花在线观看 | 日本三级高清视频 | 少妇videos另类 |