大家好,真的好久不见………………因为现在暑假实习太忙,基本没太有时间写自己的东西了
悲剧……所以一直想要重构 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 下的算法~