<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行代碼實現(xiàn)React核心調(diào)度功能

          共 4800字,需瀏覽 10分鐘

           ·

          2021-12-19 14:15

          作者:卡頌

          簡介:《React技術(shù)揭秘》作者

          來源:SegmentFault  思否社區(qū)


          大家好,我卡頌。


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


          這套調(diào)度系統(tǒng)的基本功能包括:


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

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

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


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


          我知道你不喜歡看大段的代碼,所以本文會以圖+代碼片段的形式講解原理。


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


          開整!


          準(zhǔn)備工作



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


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


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


          所以,對于如下work


          const work1 = {
            count: 100
          }


          代表:執(zhí)行100次insertItem向頁面插入100個<span/>


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


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



          包括三步:


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

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

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


          代碼如下:

          // 保存所有work的隊列
          const workList: work[] = [];

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

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

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

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

          點擊button就能插入100個<span/>。

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

          接下來我們將其改造成異步的。

          Scheduler



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

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

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

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

          • ImmediatePriority,最高的同步優(yōu)先級

          • UserBlockingPriority

          • NormalPriority

          • LowPriority

          • IdlePriority,最低優(yōu)先級


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

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

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

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

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

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

          比如,ImmediatePrioritytimeout為-1,由于:

          startTime - 1 < startTime

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

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

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

          用Scheduler改造Demo



          改造后的流程如圖:


          改造前,work直接從workList隊列尾取出:

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

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

          比如,如下work代表以NormalPriority優(yōu)先級插入100個\<span/\>:

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

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

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

          改造后流程的變化



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

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

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

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

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

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

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

          needYield方法的實現(xiàn)(何時會中斷)請參考文末在線Demo


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



          舉例來看一個高優(yōu)先級打斷低優(yōu)先級的例子:

          • 插入一個低優(yōu)先級work,屬性如下


          const work1 = {
            count: 100,
            priority: LowPriority
          }

          • 經(jīng)歷schedule(調(diào)度),perform(執(zhí)行),在執(zhí)行了80次工作時,突然插入一個高優(yōu)先級work,此時:


          const work1 = {
            // work1已經(jīng)執(zhí)行了80次工作,還差20次執(zhí)行完
            count: 20,
            priority: LowPriority
          }
          // 新插入的高優(yōu)先級work
          const work2 = {
            count: 100,
            priority: ImmediatePriority
          }

          • work1工作中斷,繼續(xù)schedule。由于work2優(yōu)先級更高,會進(jìn)入work2對應(yīng)perform,執(zhí)行100次工作

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


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

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

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


          之所以要區(qū)分宏/微觀,是因為微觀的打斷不一定意味著宏觀的打斷。

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

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

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



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


          對照流程圖來看:


          總結(jié)



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

          • schedule
          • perform

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




          點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  六月婷婷在线 | 青青草A∨在线视频免费 | 中日韩特黄A片免费视频 | 操逼网操逼 | 一级a一级a爰片免费 |