【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,则不会进行递归调用,一直在主栈中进行调用,浏览器中看到的数字不会闪动,而是一出来已经确定的数字。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值