<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React內(nèi)部的性能優(yōu)化沒(méi)有達(dá)到極致?

          共 2980字,需瀏覽 6分鐘

           ·

          2022-03-15 18:46

          對(duì)于如下這個(gè)常見(jiàn)交互步驟:

          1. 點(diǎn)擊按鈕,觸發(fā)狀態(tài)更新

          2. 組件render

          3. 視圖渲染

          你覺(jué)得哪些步驟有「性能優(yōu)化的空間」呢?

          答案是:1和2。

          對(duì)于「步驟1」,如果狀態(tài)更新前后沒(méi)有變化,則可以略過(guò)剩下的步驟。這個(gè)優(yōu)化策略被稱(chēng)為eagerState。

          對(duì)于「步驟2」,如果組件的子孫節(jié)點(diǎn)沒(méi)有狀態(tài)變化,可以跳過(guò)子孫組件的render。這個(gè)優(yōu)化策略被稱(chēng)為bailout。

          看起來(lái)eagerState的邏輯很簡(jiǎn)單,只需要比較「狀態(tài)更新前后是否有變化」。

          然而,實(shí)踐上卻很復(fù)雜。

          本文通過(guò)了解eagerState的邏輯,回答一個(gè)問(wèn)題:React的性能優(yōu)化達(dá)到極致了么?

          一個(gè)奇怪的例子

          考慮如下組件:

          function?App()?{
          ??const?[num,?updateNum]?=?useState(0);
          ??console.log("App?render",?num);

          ??return?(
          ????<div?onClick={()?=>?updateNum(1)}>
          ??????<Child?/>
          ????div>

          ??);
          }

          function?Child()?{
          ??console.log("child?render");
          ??return?<span>childspan>;
          }

          在線Demo地址[1]

          首次渲染,打?。?/p>

          App?render?0
          child?render?

          第一次點(diǎn)擊div,打?。?/p>

          App?render?1
          child?render?

          第二次點(diǎn)擊div,打?。?/p>

          App?render?1

          第三、四......次點(diǎn)擊div,不打印

          「第二次」點(diǎn)擊中,打印了App render 1,沒(méi)有打印child render。代表App的子孫組件沒(méi)有render,命中了bailout。

          「第三次及之后」的點(diǎn)擊,什么都不打印,代表沒(méi)有組件render,命中了eagerState。

          那么問(wèn)題來(lái)了,明明第一、二次點(diǎn)擊都是執(zhí)行updateNum(1),顯然狀態(tài)是沒(méi)有變化的,為什么第二次沒(méi)有命中eagerState?

          eagerState的觸發(fā)條件

          首先我們需要明白,為什么叫eagerState(急迫的狀態(tài))?

          通常,什么時(shí)候能獲取到最新?tīng)顟B(tài)呢?組件render的時(shí)候。

          當(dāng)組件render,useState執(zhí)行并返回最新?tīng)顟B(tài)。

          考慮如下代碼:

          const?[num,?updateNum]?=?useState(0);

          useState執(zhí)行后返回的num就是最新?tīng)顟B(tài)。

          之所以useState執(zhí)行時(shí)才能計(jì)算出最新?tīng)顟B(tài),是因?yàn)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(145, 109, 213);font-weight: bolder;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;">狀態(tài)是根據(jù)「一到多個(gè)更新」計(jì)算而來(lái)的。

          比如,在如下點(diǎn)擊事件中觸發(fā)3個(gè)更新:

          const?onClick?=?()?=>?{
          ??updateNum(100);
          ??updateNum(num?=>?num?+?1);
          ??updateNum(num?=>?num?*?2);
          }

          組件render時(shí)num最新?tīng)顟B(tài)應(yīng)該是多少呢?

          • 首先num變?yōu)?00

          • 100 + 1 = 101

          • 101 * 2 = 202

          所以,useState會(huì)返回202作為num的最新?tīng)顟B(tài)。

          實(shí)際情況會(huì)更復(fù)雜,更新擁有自己的優(yōu)先級(jí),所以在render前不能確定「究竟是哪些更新會(huì)參與狀態(tài)的計(jì)算」。

          所以,在這種情況下組件必須render,useState必須執(zhí)行才能知道num的最新?tīng)顟B(tài)是多少。

          那就沒(méi)法提前將num的最新?tīng)顟B(tài)num的當(dāng)前狀態(tài)比較,判斷「狀態(tài)是否變化」。

          eagerState的意義在于,在「某種情況」下,我們可以在組件render前就提前計(jì)算出最新?tīng)顟B(tài)(這就是eagerState的由來(lái))。

          這種情況下組件不需要render就能比較「狀態(tài)是否變化」

          那么是什么情況呢?

          答案是:當(dāng)前組件上「不存在更新」的時(shí)候。

          當(dāng)不存在更新時(shí),本次更新就是組件的第一個(gè)更新。在只有一個(gè)更新的情況下是能確定最新?tīng)顟B(tài)的。

          所以,eagerState的前提是:

          當(dāng)前組件不存在更新,那么首次觸發(fā)狀態(tài)更新時(shí),就能立刻計(jì)算出最新?tīng)顟B(tài),進(jìn)而與當(dāng)前狀態(tài)比較。

          如果兩者一致,則省去了后續(xù)render的過(guò)程。

          這就是eagerState的邏輯。但遺憾的是,實(shí)際情況還要再?gòu)?fù)雜一丟丟。

          先讓我們看一個(gè)「看似不相干」的例子。

          必要的React源碼知識(shí)

          對(duì)于如下組件:

          function?App()?{
          ??const?[num,?updateNum]?=?useState(0);
          ??window.updateNum?=?updateNum;

          ??return?<div>{num}div>;
          }

          在控制臺(tái)執(zhí)行如下代碼,可以改變視圖顯示的num么?

          window.updateNum(100)

          答案是:可以。

          因?yàn)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(145, 109, 213);font-weight: bolder;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;">App組件對(duì)應(yīng)fiber(保存組件相關(guān)信息的節(jié)點(diǎn))已經(jīng)被作為「預(yù)設(shè)的參數(shù)」傳遞給window.updateNum了:

          //?updateNum的實(shí)現(xiàn)類(lèi)似這樣
          //?其中fiber就是App對(duì)應(yīng)fiber
          const?updateNum?=?dispatchSetState.bind(null,?fiber,?queue);

          所以updateNum執(zhí)行時(shí)是能獲取App對(duì)應(yīng)fiber的。

          然而,一個(gè)組件實(shí)際有2個(gè)fiber,他們:

          • 一個(gè)保存「當(dāng)前視圖」對(duì)應(yīng)的相關(guān)信息,被稱(chēng)為current fiber

          • 一個(gè)保存「接下來(lái)要變化的視圖」對(duì)應(yīng)的相關(guān)信息,被稱(chēng)為wip fiber

          updateNum中被預(yù)設(shè)的是wip fiber。

          當(dāng)組件觸發(fā)更新后,會(huì)在組件對(duì)應(yīng)的2個(gè)fiber上都「標(biāo)記更新」。

          當(dāng)組件render時(shí),useState會(huì)執(zhí)行,計(jì)算出新的狀態(tài),并把wip fiber上的「更新標(biāo)記」清除。

          當(dāng)視圖完成渲染后,current fiberwip fiber會(huì)交換位置(也就是說(shuō)本次更新的wip fiber會(huì)變?yōu)橄麓胃碌?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(145, 109, 213);font-weight: bolder;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;">current fiber)。

          回到例子

          剛才談到,eagerState的前提是:「當(dāng)前組件不存在更新」。

          具體來(lái)講,是組件對(duì)應(yīng)的current fiberwip fiber都不存在更新。

          回到我們的例子:

          第一次點(diǎn)擊div,打?。?/p>

          App?render?1
          child?render?

          current fiberwip fiber同時(shí)標(biāo)記更新。

          renderwip fiber「更新標(biāo)記」清除。

          此時(shí)current fiber還存在「更新標(biāo)記」。

          完成渲染后,current fiberwip fiber會(huì)交換位置。

          變成:wip fiber存在更新,current fiber不存在更新。

          所以第二次點(diǎn)擊div時(shí),由于wip fiber存在更新,沒(méi)有命中eagerState,于是打?。?/p>

          App?render?1

          renderwip fiber「更新標(biāo)記」清除。

          此時(shí)兩個(gè)fiber上都不存在「更新標(biāo)記」。所以后續(xù)點(diǎn)擊div都會(huì)觸發(fā)eagerState,組件不會(huì)render。

          總結(jié)

          由于React內(nèi)部各個(gè)部分間互相影響,導(dǎo)致React性能優(yōu)化的結(jié)果有時(shí)讓開(kāi)發(fā)者迷惑。

          為什么沒(méi)有聽(tīng)到多少人抱怨呢?因?yàn)樾阅軆?yōu)化只會(huì)反映在指標(biāo)上,不會(huì)影響交互邏輯。

          通過(guò)本文我們發(fā)現(xiàn),React性能優(yōu)化并沒(méi)有做到極致,由于存在兩個(gè)fiber,eagerState策略并沒(méi)有達(dá)到最理想的狀態(tài)。

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线激情视频 | 色狂c熟妇中国日本 | 午夜剧场污| 中文字幕三级 | 亚洲有码在线视频 |