React源码分析

265 阅读2分钟

vdom解决三个问题

  • 高效的diff算法
  • 只要更新需要更新的dom节点
  • 数据变化监测, batch dom读写操作

diff算法

  • 递归: O(n^3)
  • react vdom diff O(n)

diff策略

  • 很少跨层,所以我们就同层比较,不跨层比较
  • 相同的Component又相同的tree结构,不同的Component有不同的tree结构
  • 同一层级的子节点,key

对应tree diff、component diff、element diff

tree diff

updateDepth: 对dom树的层级进行控制

Q: 如果有跨层级的移动,应该如何处理?

先对应的创建,再删除

component diff

  • same component
  • different component, dirty component, 替换所有的子节点
  • shouldComponentUpdate()

element diff

  • insert_markup, move_existing, delete_node
move_existing

generateComponentChildren 调用 receiveComponent, prev = next

from: A, B, C, D

to: B, A, D, C

key

for..in遍历新的拿到key, 去旧的节点去找如果存在就移动,移动之前会

lastindex, 顺序优化:如果新的节点的位置大于旧的lastindex说明是新增

总结

  • O(n^3)=> O(n)
  • 避免将最后一个元素移动到最前面
  • 分层比较 tree diff
  • Component diff tree diff
  • key => element diff
  • 建议:开发组件,dom结构稳定,提升性能
  • 建议:避免直接将行尾元素插到最前面
  • shouldCompoentUpdate 需不需要做diff

snabbdom github库 (vue2.0借鉴)

双端比较算法

inferno.js 号称最快的diff算法 (vue3.0借鉴)

abcd dabc 只移动d,然而react却需要移动abc,这也是react设计不好的地方

调度

fiber

  • vdom层
  • reconciler层 做一些dom diff 生命周期钩子一些复杂的运算
  • render层 reactDOM、rn

作用就是帮你做一些切片

在setState的时候,开始协调,遍历你的vdom,然后去diff,然后拿到真实的dom,然后紧接着传给render去渲染

time slice

requestIdleCallback && requestAnimationFrame

  • requestIdleCallback: 浏览器空闲的时候去调用,回调不一定去执行

正常浏览器一帧都进行那些操作:

  1. 交互操作(touch,wheel blocking input event === click、keypress Non Blocking input event)
  2. Timers Js执行
  3. begin frame (window resize、scroll、mediaquery change、animation events)
  4. rAF (requestAnimationFrame Callbacks、Intersection Observer Callbacks)调用
  5. Layout(1. Recalc style 2. Update layout 3. Resize Observer Callbacks)
  6. Paint(1. Compositing Update 2. Paint Invilidation 3. Record)

idleCallBack执行位置

input => rAF => frame commit => idle period(idleCallback,idleCallback) => input...循环

react fiber reconciler(调度)

  • 计算任务的time(expirationTime)
  • requestIdleCallback polyfill版本

MessageChannel

渲染以后宏任务先执行

requestAnimationFrame解决多次(后台不执行可以使用settimeout替换)+ 计算frame时间和下个frame时间 + messageChannel

expirationTime

当前时间(performance.now()) + 常量(任务优先级)

eventemitter.js