【React】React源碼梳理筆記(九)

前言

  • 本篇梳理下fiber架構的基礎知識。

requestIdleCallback

  • 這玩意只在瀏覽器中有,一般情況,瀏覽器在解析js時,如果任務太耗時,就會阻塞用戶操作,界面成卡死狀態。就像我前面無聊寫的那個對賬工具,計算量太大頁面完全定住沒法動。

  • 而 requestIdleCallback,雖然不能解決js卡死問題,但如果將大的js任務拆解成一個個小的js任務,然後通過這個api進行分段調用,即可讓用戶覺得好像不卡一樣。

  • 這個api有2個參數,一個就是需要調用的函數,一個是超時設置,mdn介紹

  • 就是當瀏覽器空閒了,就會調用此函數,如果設置了deadline時間,那麼不管瀏覽器是不是在忙,都得調用這個函數。當然瀏覽器在忙的時候是無法插入此函數的,只能走到空閒時間片時強制執行此函數。沒看懂我這句的看mdn。

  • 這個callback函數可以接收到一個deadline,裏面有2個方法。deadlinemdn

  • 一個是IdleDeadline.timeRemaining(),表示當前閒置週期的預估剩餘毫秒數。

  • 還有個IdleDeadline.didTimeout,表示是否超時,布爾值。

  • 看個例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <title>yehuozhili</title>
</head>
<body>
  <div id="root"></div>
</body>
<script>
  function sleep(delay) {
    for (let start = Date.now(); Date.now() - start <= delay;) { }
  }
  let works = [
    () => {
      console.log('11111')
      sleep(20)
      console.log('over1')
    },
    () => {
      console.log('222')
      //  sleep(20)
      console.log('over2')
    },
    () => {
      console.log('3333')
      //  sleep(29)
      console.log('over3')
    }
  ]
  function callback(deadline) {
    console.log(deadline.timeRemaining(), deadline.didTimeout)
    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0) {
      performUnitOfWork()
    }
    //走到這表示不滿足while條件,下一次時間不夠了,進行遞歸調用
    if (works.length > 0) {//當works爲空就出去了
      window.requestIdleCallback(callback, { timeout: 1000 })
    }
  }
  function performUnitOfWork() {
    works.shift()()
  }
  window.requestIdleCallback(callback, { timeout: 1000 })
</script>
</html>
  • 可以發現最後輸出:
index.html:75 14.42 false
index.html:59 11111
index.html:61 over1
index.html:75 9.28 false
index.html:64 222
index.html:66 over2
index.html:69 3333
index.html:71 over3
  • 我們第一個任務的sleep(20)的時間超過了1幀所剩餘的14.42ms,所以當執行完畢後,dealine.timeRemaining()會等於0,這樣時間不夠執行下一個任務,所以進入下面的遞歸idle調用判斷。
  • 這樣遞歸idle,就可以把執行權交還給瀏覽器,使得瀏覽器看起來沒死機的樣子,然後當瀏覽器空閒時,進行遞歸完成下一個任務。
  • 由於任務2和3我都把sleep註釋掉了,所以任務2執行完,deadline.timeRemaining剩餘時間大於0,所以夠執行下一個任務,繼續在while循環裏運行下一個任務。因爲在while裏連續執行而不是遞歸調用,所以中間沒有再次打印console.log那段。
  • React的fiber就是基於這種思想,把原本diff的不可中斷遞歸操作,拆解成多個小任務進行遞歸調用,使得diff的過程不會卡住瀏覽器。雖然計算量不變,但用戶體驗更好了。

MessageChannel

  • 我們知道只有谷歌瀏覽器支持idle,react肯定不能搞得只能給谷歌瀏覽器用,所以兼容有使用messageChannel來製作相同功能。
  • mdn地址
  • 這玩意其實就是個宏任務,vue的nextTick以前是這玩意搞得,還看見有人用來搞深拷貝的騷操作。
  • 一般瀏覽器都是會清空微任務,然後從宏任務隊列裏一個一個拿出來執行宏任務,如果有碰到微任務立馬執行。而瀏覽器渲染是在宏任務執行之前。
  • 所以,宏任務變相等於idle,不會阻塞渲染。
  • 看個hack例子:
 function sleep(delay) {
    for (let start = Date.now(); Date.now() - start <= delay;) { }
  }
  let root = document.getElementById('root')
  let works = [
    () => {
      console.log('11111')
      root.textContent = '111'
      sleep(20)
      console.log('over1')
    },
    () => {
      console.log('222')
      // sleep(20)
      root.textContent = '2222'
      console.log('over2')
    },
    () => {
      console.log('3333')
      root.textContent = '3333'
      sleep(29)
      console.log('over3')
    },
    () => {
      console.log('444')
      root.textContent = '4444'
      console.log('over4')
    }
  ]
  function callback(deadline) {
    console.log(deadline.timeRemaining(), deadline.didTimeout)
    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0) {
      performUnitOfWork()
    }
    //走到這表示不滿足while條件,下一次時間不夠了,進行遞歸調用
    if (works.length > 0) {//當works爲空就出去了
      // window.requestIdleCallback(callback, { timeout: 1000 })
      window.myIdle(callback, { timeout: 1000 })
    }
  }
  function performUnitOfWork() {
    works.shift()()
  }
  //window.requestIdleCallback(callback, { timeout: 1000 })

  let channel = new MessageChannel()
  let perFrameTime = (1000 / 60);
  let timeRemaining = () => perFrameTime - (Date.now() - startTime);//1幀減去當前減開始
  let pendingCallback;
  let timeoutTime, startTime
  channel.port2.onmessage = () => {//接收消息
    if (pendingCallback) {
      pendingCallback({ didTimeout: Date.now() > timeoutTime, timeRemaining });
    }
  }
  window.myIdle = (callback, options) => {
    timeoutTime = Date.now() + options.timeout;//現在時間戳+傳入
    startTime = Date.now();
    pendingCallback = callback;//賦值callback
    channel.port1.postMessage('');

    // startTime = Date.now();
    // setTimeout(() => {
    //   callback({ didTimeout: Date.now() > timeoutTime, timeRemaining });
    // });

  }
  window.myIdle(callback, { timeout: 1000 })
  • 還是剛纔那些代碼,只是寫了個myidle代替requestIdleCallback。
  • 這裏不用channel用setTimeout一樣效果。
  • 這裏速度有點快,可以發現數字會有個閃動過程,這就代表沒有阻塞渲染。
  • 而阻塞渲染會怎樣呢?將while循環內改爲true,則不會進行遞歸調用,一直在主棧中進行調用,瀏覽器中看到的數字不會閃動,而是一出來已經確定的數字。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章