<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>

          100行代碼實(shí)現(xiàn)React核心調(diào)度功能

          共 3106字,需瀏覽 7分鐘

           ·

          2022-05-28 00:21

          想必大家都知道React有一套基于Fiber架構(gòu)的調(diào)度系統(tǒng)。這套調(diào)度系統(tǒng)的基本功能包括:

          • 更新有不同優(yōu)先級(jí)

          • 一次更新可能涉及多個(gè)組件的render,這些render可能分配到多個(gè)宏任務(wù)中執(zhí)行(即時(shí)間切片

          • 高優(yōu)先級(jí)更新會(huì)打斷進(jìn)行中的低優(yōu)先級(jí)更新

          本文會(huì)用100行代碼實(shí)現(xiàn)這套調(diào)度系統(tǒng),讓你快速了解React的調(diào)度原理。

          我知道你不喜歡看大段的代碼,所以本文會(huì)以+代碼片段的形式講解。

          文末有完整的在線Demo,你可以自己上手玩玩。

          開整!

          準(zhǔn)備工作

          我們用work這一數(shù)據(jù)結(jié)構(gòu)代表一份工作,work.count代表這份工作要重復(fù)做某件事的次數(shù)。

          Demo中要重復(fù)做的事是“執(zhí)行insertItem方法,向頁(yè)面插入”:

          const?insertItem?=?(content:?string)?=>?{
          ??const?ele?=?document.createElement('span');
          ??ele.innerText?=?`${content}`;
          ??contentBox.appendChild(ele);
          };

          所以,對(duì)于如下work

          const?work1?=?{
          ??count:?100
          }

          代表:執(zhí)行100次insertItem向頁(yè)面插入100個(gè)

          work可以類比React的一次更新work.count類比這次更新要render的組件數(shù)量。所以Demo是對(duì)React更新流程的類比

          來(lái)實(shí)現(xiàn)第一版的調(diào)度系統(tǒng),流程如圖:

          包括三步:

          1. workList隊(duì)列(用于保存所有work)插入work

          2. schedule方法從workList中取出work,傳遞給perform

          3. perform方法執(zhí)行完work的所有工作后重復(fù)步驟2

          代碼如下:

          //?保存所有work的隊(duì)列
          const?workList:?work[]?=?[];

          //?調(diào)度
          function?schedule()?{
          ??//?從隊(duì)列尾取一個(gè)work
          ??const?curWork?=?workList.pop();
          ??
          ??if?(curWork)?{
          ????perform(curWork);
          ??}
          }

          //?執(zhí)行
          function?perform(work:?Work)?{
          ??while?(work.count)?{
          ????work.count--;
          ????insertItem();
          ??}
          ??schedule();
          }

          為按鈕綁定點(diǎn)擊交互,最基本的調(diào)度系統(tǒng)就完成了:

          button.onclick?=?()?=>?{
          ??workList.unshift({
          ????count:?100
          ??})
          ??schedule();
          }

          點(diǎn)擊button就能插入100個(gè)

          React類比就是:點(diǎn)擊button,觸發(fā)同步更新,100個(gè)組件render

          接下來(lái)我們將其改造成異步的。

          Scheduler

          React內(nèi)部使用Scheduler完成異步調(diào)度。

          Scheduler是獨(dú)立的包。所以可以用他改造我們的Demo

          Scheduler預(yù)置了5種優(yōu)先級(jí),從上往下優(yōu)先級(jí)降低:

          • ImmediatePriority,最高的同步優(yōu)先級(jí)
          • UserBlockingPriority
          • NormalPriority
          • LowPriority
          • IdlePriority,最低優(yōu)先級(jí)

          scheduleCallback方法接收優(yōu)先級(jí)與回調(diào)函數(shù)fn,用于調(diào)度fn

          //?將回調(diào)函數(shù)fn以LowPriority優(yōu)先級(jí)調(diào)度
          scheduleCallback(LowPriority,?fn)

          Scheduler內(nèi)部,執(zhí)行scheduleCallback后會(huì)生成task這一數(shù)據(jù)結(jié)構(gòu):

          const?task1?=?{
          ??expiration:?startTime?+?timeout,
          ??callback:?fn
          }

          task1.expiration代表task1的過(guò)期時(shí)間,Scheduler會(huì)優(yōu)先執(zhí)行過(guò)期的task.callback

          expirationstartTime為當(dāng)前開始時(shí)間,不同優(yōu)先級(jí)的timeout不同。

          比如,ImmediatePrioritytimeout為-1,由于:

          startTime?-?1?

          所以ImmediatePriority會(huì)立刻過(guò)期,callback立刻執(zhí)行。

          IdlePriority對(duì)應(yīng)timeout為1073741823(最大的31位帶符號(hào)整型),其callback需要非常長(zhǎng)時(shí)間才會(huì)執(zhí)行。

          callback會(huì)在新的宏任務(wù)中執(zhí)行,這就是Scheduler調(diào)度的原理。

          用Scheduler改造Demo

          改造后的流程如圖:

          改造前,work直接從workList隊(duì)列尾取出:

          //?改造前
          const?curWork?=?workList.pop();

          改造后,work可以擁有不同優(yōu)先級(jí),通過(guò)priority字段表示。

          比如,如下work代表「以NormalPriority優(yōu)先級(jí)插入100個(gè)

          const?work1?=?{
          ??count:?100,
          ??priority:?NormalPriority
          }

          改造后每次都使用最高優(yōu)先級(jí)的work

          //?改造后
          //?對(duì)workList排序后取priority值最小的(值越小,優(yōu)先級(jí)越高)
          const?curWork?=?workList.sort((w1,?w2)?=>?{
          ???return?w1.priority?-?w2.priority;
          })[0];

          改造后流程的變化

          由流程圖可知,Scheduler不再直接執(zhí)行perform,而是通過(guò)執(zhí)行scheduleCallback調(diào)度perform.bind(null, work)

          即,滿足一定條件的情況下,生成新task

          const?someTask?=?{
          ??callback:?perform.bind(null,?work),
          ??expiration:?xxx
          }

          同時(shí),work的工作也是可中斷的。在改造前,perform會(huì)同步執(zhí)行完work中的所有工作:

          while?(work.count)?{
          ??work.count--;
          ??insertItem();
          }

          改造后,work的執(zhí)行流程隨時(shí)可能中斷:

          while?(!needYield()?&&?work.count)?{
          ??work.count--;
          ??insertItem();
          }

          needYield方法的實(shí)現(xiàn)(何時(shí)會(huì)中斷)請(qǐng)參考文末在線Demo

          高優(yōu)先級(jí)打斷低優(yōu)先級(jí)的例子

          舉例來(lái)看一個(gè)高優(yōu)先級(jí)打斷低優(yōu)先級(jí)的例子:

          1. 插入一個(gè)低優(yōu)先級(jí)work,屬性如下
          const?work1?=?{
          ??count:?100,
          ??priority:?LowPriority
          }
          1. 經(jīng)歷schedule(調(diào)度),perform(執(zhí)行),在執(zhí)行了80次工作時(shí),突然插入一個(gè)高優(yōu)先級(jí)work,此時(shí):
          const?work1?=?{
          ??//?work1已經(jīng)執(zhí)行了80次工作,還差20次執(zhí)行完
          ??count:?20,
          ??priority:?LowPriority
          }
          //?新插入的高優(yōu)先級(jí)work
          const?work2?=?{
          ??count:?100,
          ??priority:?ImmediatePriority
          }
          1. work1工作中斷,繼續(xù)schedule。由于work2優(yōu)先級(jí)更高,會(huì)進(jìn)入work2對(duì)應(yīng)perform,執(zhí)行100次工作

          2. work2執(zhí)行完后,繼續(xù)schedule,執(zhí)行work1剩余的20次工作

          在這個(gè)例子中,我們需要區(qū)分2個(gè)「打斷」的概念:

          1. 在步驟3中,work1執(zhí)行的工作被打斷。這是微觀角度的「打斷」

          2. 由于work1被打斷,所以繼續(xù)schedule。下一個(gè)執(zhí)行工作的是更高優(yōu)的work2work2的到來(lái)導(dǎo)致work1被打斷,這是宏觀角度的「打斷」

          之所以要區(qū)分「宏/微觀」,是因?yàn)?strong style="color: rgb(145, 109, 213);">「微觀的打斷」不一定意味著「宏觀的打斷」

          比如:work1由于時(shí)間切片用盡,被打斷。沒(méi)有其他更高優(yōu)的work與他競(jìng)爭(zhēng)schedule的話,下一次perform還是work1

          這種情況下微觀下多次打斷,但是宏觀來(lái)看,還是同一個(gè)work在執(zhí)行。這就是「時(shí)間切片」的原理。

          調(diào)度系統(tǒng)的實(shí)現(xiàn)原理

          以下是調(diào)度系統(tǒng)的完整實(shí)現(xiàn)原理:

          對(duì)照流程圖來(lái)看:

          總結(jié)

          本文是React調(diào)度系統(tǒng)的簡(jiǎn)易實(shí)現(xiàn),主要包括兩個(gè)階段:

          • schedule

          • perform

          如果你對(duì)代碼的具體實(shí)現(xiàn)感興趣,下面是完整Demo地址。

          參考資料

          [1]

          Scheduler: https://github.com/facebook/react/tree/main/packages/scheduler

          [2]

          完整Demo地址: https://codesandbox.io/s/xenodochial-alex-db74g?file=/src/index.ts


          彥祖,點(diǎn)個(gè)「在看」
          瀏覽 39
          點(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>
                  狠狠操人人摸 | 小黄片视频免费 | 天天天干夜夜夜 | 青青草中文字幕 | 日本激情视频 |