浏览器刷新频率与 JS 运行时间 :
浏览器刷新频率:60HZ 也就是每秒刷新 60 次,大概16.6ms浏览器刷新一次(记住这个跟性能密切相关的数字 16.6)。
Js是单线程,由于 GUI 渲染线程和 JS 线程是互斥的,所以 JS 代码执行和浏览器布局、绘制不能同时执行。
在这 16.6ms 的时间里,浏览器既需要完成 JS 的执行,也需要完成样式的重排和重绘,如果 JS 执行的时间过长,超出了 16.6ms。 这次刷新就没有时间执行样式布局和样式绘制了,于是在页面上就会表现为卡顿。
React 15 的虚拟 DOM 和 Diff :
React 15 的架构,主要包含以下两块内容:
Reconciler:(协调器)(找不同)负责调用 render 生成虚拟 DOM,进行 Diff,找出变化后的虚拟 DOM
Renderer:(渲染器)(去更新)负责接收 Reconciler 通知,将变化的组件渲染在当前宿主环境,比如浏览器(react-dom),不同的宿主环境会有不同的 Renderer。
存在的缺陷:
当 React 状态更新时,会递归同步更新DOM 树。
如果节点非常多,即使只有一次 state 变更,React 也需要进行复杂的递归更新,更新一旦开始,中途就无法中断,直到遍历完整颗树,才能释放 JS 主线程。
如果 React 组件内容过多,就会导致在16.6 ms 内无法完成全部的状态更新而导致没有足够的时间去执行浏览器的渲染,于是在页面上就表现为卡顿。
React 15 的状态更新过程:
批处理:多次调用 setState() 会合并为一次更新。
原理:调用 setState() 并没有立即更新状态,而是存储到 _pendingStateQueue 队列中,将需要更新的组件存入 dirtyComponent 中。在非异步代码中,React 会将 isBatchingUpdates 标记设置为 true,表示批量更新;而当异步代码执行时,由于React 已经将内部的 isBatchingUpdates 标记设置为 false,所以,异步代码中操作 setState 表现为非批量更新,而是调用一次 setState 就更新一次状态、组件 其实,这是一个 bug,因为这导致 React 在不同情况下setState 表现不一致
React 为了解决这个问题,提供了一个 API 来实现手动批处理:ReactDOM.unstable_batchedUpdates()
React 16 的 Fiber 架构 :
React 16 的架构,主要包裹以下 3 块内容:
Scheduler(调度器):调度任务的优先级,高优任务优先进入 Reconciler。
Reconciler(协调器):负责找出变化的组件(不递归了,使用 Fiber 重构,可以中断)。
Renderer(渲染器):负责将变化的组件渲染到页面上。
Concurrent Mode - 并发模式 :
具体来说,React 采用了以下两个策略:
大任务拆分小任务
React 的解决思路,就是在浏览器每一帧的时间中预留一些时间给 JS 线程,React 利用这部分时间更新组件。
当预留的时间不够用时,React 将线程控制权交还给浏览器让他有时间 渲染 UI(优先级高的任务),React 则等待下一帧再继续被中断的工作。
将大任务分拆到每一帧中,每一帧执行一小部分任务的操作,就是我们常说的:时间切片。
任务划分优先级
Concurrent Mode 就是为了解决实现以上两点,设计了一套新的架构,重点就是,让组件的渲染 “可中断” 并且具有 “优先级”。
Fiber 数据结构
每个fiber对应一个react元素,每个fiber都有3个属性:child(第一个子节点),sibling(兄弟节点),return(父级节点),有点儿像双向链表。
这样,将来 React 在进行 diff 时,就可以按照 Fiber 之间的顺序,一个个的执行,比如,可以把每个 Fiber 节点看成是一个小任务。
每个小任务完成后就会看下是否还有剩余时间,如果有剩余时间就继续处理下一个 Fiber;如果没有剩余时间,就将当前处理的 Fiber 引用存储起来,然后,将控制权交还给浏览器,
由浏览器完成渲染。
然后,下一帧,React 恢复上一次的工作,接着处理下一个 Fiber 工作。所以,这个阶段的工作是可中断、可恢复执行的。
双缓存 Fiber tree
React 做更新处理时,会同时存在两颗 fiber tree,优势:复用 Fiber 节点。
一颗是已经存在的 old fiber tree,对应当前屏幕显示的内容,通过根节点 fiberRootNode 的currrent 指针可以访问,称为current fiber tree。
另外一颗是更新过程中构建的 new fiber tree,称为 workInProgress fiber tree。
这两棵树之间通过 alternate 属性连接。
currentFiber.alternate === workInProgressFiber;workInProgressFiber.alternate === currentFiber;diff 比较,就是在 构建workInProgress fiber tree 的过程中,判断 current fiber tree 中的 fiber node 是否可以被 workInProgress fiber tree 复用。
能被复用,意味在本次更新中,需要做组件的 update 以及 dom 节点的 move、update 等操作。
不可复用,则意味着需要做组件的 mount、unmount 以及 dom 节点的 insert、delete 等操作。
当更新完成以后,fiberRootNode 的 current 指针会指向 workInProgress fiber tree,作为下一次更新的 current fiber tree。