likes
comments
collection
share

学习react的更新原理

作者站长头像
站长
· 阅读数 4

前言

  • React通过JSX编写视图代码,babel会编译JXS为React.createElement的函数调用,产生虚拟DOM,这也意味着React不能像Vue那样可以静态分析视图代码。
  • React没有使用definePropertyproxy收集依赖,每次更新需要手动触发。
// React
function A() {
  return <div><span>hello world</span></div>
}
// 编译为
React.createElement('div', {}, React.createElement('span', {}, 'hello world'))

setState

  • 基本用法
this.setState({ cnt: this.state.cnt + 1 }, () => {
  // 执行完更新后执行该回调函数
})
this.setState((prevState) => ({ cnt: prevState.cnt + 1 }), () => {})
  • 源码
class Component {
  constructor() {
    this.updater = classComponentUpdater
  }
  
  setState(partialState) {
    this.updater.enqueueSetState(this, partialState)
  }
}

const classComponentUpdater = {
  enqueueSetState(instance, payload) {
    const fiber = instance._reactInternals // 获取实例的fiber
    const eventTime = performance.now() // 计算超时时间
    const lane = requestUpdateLane(fiber) // 计算本次更新的优先级
    const update = { eventTime, lane, payload } // 创建更新对象
    
    fiber.updateQueue.push(update) // 加入fiber的更新队列
    
    scheduleUpdateOnFiber(fiber)
    queueMicrotask(flushSyncCallbackQueue)
  }
}

function scheduleUpdateOnFiber(fiber) {
  const root = markUpdateLaneFromFiberRoot(fiber) // 寻找fiber的根节点,递归fiber.return节点直到fiber.return为null且fiber.tag为HostRoot
  if (root === null) {
    return null
  }
  
  ensureRootIsScheduled(root) // 创建一个任务,从根节点开始更新
}

function ensureRootIsScheduled(root) {
  let nextLanes = SyncLane // 1
  let newCallbackPriority = SyncLanePriority // 暂定最高优先级12
  let existingCallbackPriority = root.callbackPriority // 当前根节点上正在执行更新任务的优先级
  if (existingCallbackPriority === newCallbackPriority) { // 相等则复用更新,不再创建
    return
  }
  scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))
}

let syncQueue = []
function scheduleSyncCallback(cb) {
  syncQueue.push(cb)
}

function performSyncWorkOnRoot(workInProgress) { // 真正的渲染任务:比较新旧dom,更新dom
  let root = workInProgress
  while (workInProgress) {
    if (workInProgress.tag === ClassComponent) {
      const inst = workInProgress.stateNode // 获取fiber的dom实例
      inst.state = processUpdateQueue(inst, workInProgress)
      inst.render() // 进行dom diff,更新dom
    }
    workInProgress = workInProgress.child
  }
  commitRoot(root)
}

function processUpdateQueue(inst, fiber) {
  fiber.updateQueue.reduce((state, update) => {
    if (typeof update.payload === 'function') {
      update.payload = update.payload(state)
    }
    reutrn { ...state, ...update.payload } // 拿到更新后的state
  }, inst.state)
}

function commitRoot(root) {
  root.callbackPriority = NoLanePriority // 0
}

function queueMicrotask() {
  syncQueue.forEach(cb => cb())
  syncQueue = []
}

总结

  • setState是异步更新的,不选择同步更新是因为如果多次更新的话需要多次dom diff,性能不好。
  • 实现异步更新则需要一个队列收集同步更新数据,setState的时候将实例和更新参数加入更新队列方便后续进行更新,构建好更新对象加入到fiber的更新队列,之后从root fiber开始进行更新,这是因为setState手动触发的更新,react并不知道是哪里需要更新(粗粒度,Vue使用Watcher类+收集依赖可以得知),所以要从root fiber开始,执行fiber中的更新队列。
  • 紧接着开启异步更新,期间可以继续接收同步的更新数据,更新结束后进行render开始dom diff
转载自:https://juejin.cn/post/7361274099107348543
评论
请登录