fre 1.5 发布了……新的时间切片实现

855 阅读3分钟

大家好,真的好久不见………………因为现在暑假实习太忙,基本没太有时间写自己的东西了

悲剧……所以一直想要重构 fre,拖到现在才实现,下面看看这次更新主要做了什么事情:

新的时间切片

在之前的版本中,时间切片主要是借助 requestIdleCallback 完成的,它存在一些问题,比如兼容性很差(主要是小程序不支持),比如不可控(无法控制切片的帧率,丢帧也不知道),总之这个 API 肯定要搞掉的

react 的时间切片,是基于 requestAnimation + 优先级 来调度实现的,理想状态下,fre 也应该这么做

const FPS = 1000 / 60

function workLoop (startTime = 0) {
  if (startTime && performance.now() - startTime > FPS) {
    requestAnimationFrame(workLoop)
  } else {
    const nextTime = performance.now()
    nextWork = performWork(nextWork)
    if (nextWork) {
      workLoop(nextTime)
    } else {
      options.commitWork
        ? options.commitWork(pendingCommit)
        : commitWork(pendingCommit)
    }
  }
}

如上图,fre 同样基于 requestAnimationFrame 实现了一个超级小的调度,它的原理是这样的:

  • 如果一个任务它能够在一帧内完成,那可以同步的进入下一个任务
  • 如果有十个任务,他们都可以在一帧内完成,那么同步的过程也不会卡(60fps)
  • 如果有任务无法在一帧内完成,那么它的下一个任务需要开启下一帧
  • 下一帧的任务是下一个切片,由同步变成异步,保证复杂任务不干扰下一个任务

如上,短短的十行代码,切片的同时,拥有较好的兼容性,而且切片的过程还可控,以后随时可以将更新队列按照优先级进行排序

diff 算法优化

现在的 fre 可以说是孤立无援,react 的 diff 看不懂,其他框架的 diff 不适用于 fiber

本次优化主要是针对两个 case:

function App () {
  const [arr, setArr] = useState(['A', 'B','C','D'])
  return (
    <div>
      <ul>
        {arr.map(item => (
          <A key={item} val={item}/>
        ))}
      </ul>
      <button onClick={() => setArr(['B','A','D','C'])}>+</button>
    </div>
  )
}

function A(props){
  return <div>{props.val}</div>
}

如上,这个是其中一个 case ,将 key 加到组件上,此时的 key 应该转移到组件内部的

根元素(div)上,这也是为什么我们要求组件必须有一个元素包裹的原因

如果不这么做,key 加到组件上,等于没加

{
  isShow && <A />
  isShow ? <A /> : null
  isShow ? <A /> : <B />
}

这是另一个 case,之前一直想优化这种情况,发现非常困难,后来发现我是错的

这种 case 是很难优化的,所以移除掉这部分的优化,尺寸也有史以来的第一次缩减到 1.7kb

位置的变化是很困难的,只有加 key 的情况可以做到位置变化

fix bug

终于搞懂了 useMemo / useCallback / useEffect 的关系,之前的实现有点点问题,新版本处理掉了

useMemo 是根据第二个参数判断是否需要重新计算并返回一个缓存过的 值

useCallback是根据第二个参数判断是否需要重新生成并返回一个缓存过的 函数引用

useEffect 是根据第二个参数判断是否需要执行这个函数

总结 综上所述,fre 新版本逻辑更加精巧了,我也按照约定,搞了时间切片和调度,至此,fre 算得上麻雀虽小,五脏俱全了

github 地址:github.com/132yse/fre

孤立无援,欢迎老铁加入进来,一起研究 fiber 下的算法~