课程地址 https://xiaochen1024.com/courseware/60b1b2f6cf10a4003b634718/60b1b340cf10a4003b63471f
react16之后就有了scheduler进行时间片的调度
,给每个task(工作单元)一定的时间
,如果在这个时间内没执行完,也要交出执行权给浏览器进行绘制和重排,所以异步可中断的更新
需要一定的数据结构在内存中来保存工作单元的信息
,这个数据结构就是Fiber。
Fiber需要完成的事件
- 工作单元 任务分解:Fiber最重要的功能就是作为工作单元,
保存原生节点或者组件节点对应信息(包括优先级)
,这些节点通过指针的形似形成Fiber树 - 增量渲染:
通过jsx对象和current Fiber的对比,生成最小的差异补丁,应用到真实节点上
- 根据优先级暂停、继续、排列优先级:
Fiber节点上保存了优先级
,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力
,也为上层实现批量更新、Suspense提供了基础 - 保存状态:因为Fiber能保存状态和更新的信息,所以就能实现
函数组件的状态更新
,也就是hooks
//ReactFiber.old.js
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
//作为静态的数据结构 保存节点的信息
this.tag = tag;//对应组件的类型
this.key = key;//key属性
this.elementType = null;//元素类型
this.type = null;//func或者class
this.stateNode = null;//真实dom节点
//作为fiber树架构 连接成fiber树
this.return = null;//指向父节点
this.child = null;//指向child
this.sibling = null;//指向兄弟节点
this.index = 0;
this.ref = null;
//用作为工作单元 来计算state
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
//effect相关
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
//优先级相关的属性
this.lanes = NoLanes;
this.childLanes = NoLanes;
//current和workInProgress的指针
this.alternate = null;
}
Fiber
可以保存真实的dom
,真实dom对应在内存中的Fiber节点会形成Fiber树
,这颗Fiber树在react中叫current Fiber
,也就是当前dom树对应的Fiber树
,而正在构建Fiber树
叫workInProgress Fiber
,这两颗树的节点通过alternate
相连.
构建workInProgress Fiber
发生在createWorkInProgress
中,它能创建或者复用Fiber
//ReactFiber.old.js
export function createWorkInProgress
- 在mount时:会创建fiberRoot和rootFiber,然后
根据jsx对象创建Fiber节点
,节点连接成current Fiber树
。 fiberRoot:指整个应用的根节点,只存在一个 rootFiber:ReactDOM.render或者ReactDOM.unstable_createRoot创建出来的应用的节点,可以存在多个。 - 在update时:
会根据新的状态形成的jsx(ClassComponent的render或者FuncComponent的返回值)
和current Fiber``对比(diff算法)形成一颗叫workInProgress的Fiber树
,然后将fiberRoot的current指向workInProgress树,此时workInProgress就变成了current Fiber。
静态标记
,upadte性能提升1.32倍,ssr提升23倍
编译模板的静态标记,静态节点不需要遍历,这个就是vue3优秀性能的主要来源
事件缓存
,传入的事件会自动生成并缓存一个内联函数再cache里,变为一个静态节点。这样就算我们自己写内联函数,也不会导致多余的重复渲染
静态提升
,把静态节点运算结果提升到组件外,组件每次render就重复利用静态节点的虚拟dom
Vue1.x
没有vdom
,完全的响应式,每个数据变化,都通过响应式通知机制来新建 Watcher
干活,就像独立团规模小的时候,每个战士入伍和升职,都主动通知咱老李,管理方便
项目规模变大后,过多的 Watcher
,会导致性能的瓶颈
React15x
整个新数据和老的数据做diff
,算出差异 就知道怎么去修改dom
了,就像老李指挥室有一个模型,每次人事变更,通过对比所有人前后差异,就知道了变化, 看起来有很多计算量,但是这种 immutable
的数据结构对大型项目比较友好,而且 vdom
抽象成功后,换成别的平台render成为了可能,无论是打鬼子还是打国军,都用一个 vdom
模式
碰到的问题一样,如果 dom
节点持续变多,每次 diff
的时间超过了 16ms
,就可能会造成卡顿(60fps)
Vue2.x 引入vdom,控制了颗粒度,组件层面走watcher通知, 组件内部走vdom做diff,既不会有太多watcher,也不会让vdom的规模过大,diff超过16ms,真是优秀啊 就像独立团大了以后,只有营长排长级别的变动,才会通知老李,内部的自己diff管理了
React 16 Fiber
React
走了另外一条路,既然主要问题是diff
导致卡顿,于是React
走了类似cpu
调度的逻辑,把vdom
这棵树,微观变成了链表,利用浏览器的空闲时间来做diff
,如果超过了16ms
,有动画或者用户交互的任务,就把主进程控制权还给浏览器,等空闲了继续,特别像等待女神的备胎
diff
的逻辑,变成了单向的链表,任何时候主线程女神有空了,我们在继续蹭上去接盘做 diff
,大家研究下requestIdleCallback
就知道,从浏览器角度看 是这样的
Vue3
Vue3
通过Proxy
响应式+组件内部vdom
+静态标记,把任务颗粒度控制的足够细致,所以也不太需要time-slice
了