Skip to content

UpdateQueue

简介

React节点状态存储在fibermemoizedState属性当中,而老fiber节点到新fiber节点的更新操作则存储在fiberupdateQueue属性中。不同类型节点的updateQueue存储内容格式不相同,所对应的功能也有所差异。具体如下:

  • class组件主要处理state的更新。
  • 函数组件主要处理effectcreate、destroy回调函数。
  • 普通标签主要处理属性的变动。

ClassComponent/HostRoot

ClassComponentHostRoot两者更新的相关代码都存放在react-reconciler包的ReactUpdateQueue.new.js文件当中。下面分别介绍比较重要的几个函数。

initializeUpdateQueue

javascript
export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    // 初始化状态
    baseState: fiber.memoizedState,
    // 记录第一个更新,与优先级相关
    firstBaseUpdate: null,
    // 记录最后一个更新,与优先级相关
    lastBaseUpdate: null,
   // 存放更新的具体内容
    shared: {
      // 一个或多个更新形成的循环链表
      pending: null,
      // 如果当前处于 render 阶段,此时产生的更新会放在 interleaved 中
      // 在render结束时,interleaved 变为 pending queue
      interleaved: null,
      // 更新赛道
      lanes: NoLanes,
    },
    // 记录更新内容回调
    effects: null,
  };
  // 更新队列
  fiber.updateQueue = queue;
}
export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    // 初始化状态
    baseState: fiber.memoizedState,
    // 记录第一个更新,与优先级相关
    firstBaseUpdate: null,
    // 记录最后一个更新,与优先级相关
    lastBaseUpdate: null,
   // 存放更新的具体内容
    shared: {
      // 一个或多个更新形成的循环链表
      pending: null,
      // 如果当前处于 render 阶段,此时产生的更新会放在 interleaved 中
      // 在render结束时,interleaved 变为 pending queue
      interleaved: null,
      // 更新赛道
      lanes: NoLanes,
    },
    // 记录更新内容回调
    effects: null,
  };
  // 更新队列
  fiber.updateQueue = queue;
}

该函数主要用于初始化一个更新队列updateQueue

createUpdate

javascript
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
  const update: Update<*> = {
    // 更新创建开始时间
    eventTime,
    // 该更新的 lane
    lane,

    // 更新的标识符
    tag: UpdateState,
    // 更新的内容
    payload: null,
    // 更新的回调
    callback: null,

    // 指向下一个更新
    next: null,
  };
  return update;
}
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
  const update: Update<*> = {
    // 更新创建开始时间
    eventTime,
    // 该更新的 lane
    lane,

    // 更新的标识符
    tag: UpdateState,
    // 更新的内容
    payload: null,
    // 更新的回调
    callback: null,

    // 指向下一个更新
    next: null,
  };
  return update;
}

createUpdate主要作用是创建一个更新。

enqueueUpdate

enqueueUpdate主要作用是将新增的更新(Update)添加到循环链表中,并存放到updateQueue.shared.pending上,其核心代码如下:

javascript
const pending = sharedQueue.pending;
if (pending === null) {
  update.next = update;
} else {
  update.next = pending.next;
  pending.next = update;
}
sharedQueue.pending = update;
const pending = sharedQueue.pending;
if (pending === null) {
  update.next = update;
} else {
  update.next = pending.next;
  pending.next = update;
}
sharedQueue.pending = update;

每次添加完update后,pending都会指向这个最新添加的update。由于是循环链表,所以当前的pending的next指向的是第一个更新。

processUpdateQueue

processUpdateQueue主要作用是执行updateQueue来更新state。第一段代码如下:

javascript
if (pendingQueue !== null) {
  queue.shared.pending = null;
  // 找到最先的 update,然后将最后的一个 update 的 next 断开
  const lastPendingUpdate = pendingQueue;
  const firstPendingUpdate = lastPendingUpdate.next;
  lastPendingUpdate.next = null;
  if (lastBaseUpdate === null) {
    firstBaseUpdate = firstPendingUpdate;
  } else {
    lastBaseUpdate.next = firstPendingUpdate;
  }
  lastBaseUpdate = lastPendingUpdate;
}
if (pendingQueue !== null) {
  queue.shared.pending = null;
  // 找到最先的 update,然后将最后的一个 update 的 next 断开
  const lastPendingUpdate = pendingQueue;
  const firstPendingUpdate = lastPendingUpdate.next;
  lastPendingUpdate.next = null;
  if (lastBaseUpdate === null) {
    firstBaseUpdate = firstPendingUpdate;
  } else {
    lastBaseUpdate.next = firstPendingUpdate;
  }
  lastBaseUpdate = lastPendingUpdate;
}

这段代码可以用下图来表示:

img

注意:其中置为null表示那一条指向是被切断了,也就是不存在该指向了。另外shared.pending是指向最后一次添加的update的

紧接着就是一个while循环遍历所有的update来处理state。在循环中有一个判断:

javascript
if (!isSubsetOfLanes(renderLanes, updateLane)) {}
if (!isSubsetOfLanes(renderLanes, updateLane)) {}

该判断主要是判断当前更新对应的lane是否在renderLanes中,如果不在,那么这次就不应该执行该更新。举一个例子,现在有A,C,B,D四个更新形成的链表,而当前的renderLanes只有A,B两个符合时,此时执行到更新C的时候,会先克隆一个update:

javascript
const clone: Update<State> = {
  eventTime: updateEventTime,
  lane: updateLane,

  tag: update.tag,
  payload: update.payload,
  callback: update.callback,

  next: null,
};
const clone: Update<State> = {
  eventTime: updateEventTime,
  lane: updateLane,

  tag: update.tag,
  payload: update.payload,
  callback: update.callback,

  next: null,
};

然后将这个update形成链表,并且用newFirstBaseUpdatenewLaseBaseUpdate表示链表的头尾。

javascript
// lane不符合,那么记录下当前的 update
if (newLastBaseUpdate === null) {
  newFirstBaseUpdate = newLastBaseUpdate = clone;
  newBaseState = newState;
} else {
  newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// lane不符合,那么记录下当前的 update
if (newLastBaseUpdate === null) {
  newFirstBaseUpdate = newLastBaseUpdate = clone;
  newBaseState = newState;
} else {
  newLastBaseUpdate = newLastBaseUpdate.next = clone;
}

需要注意的是,一旦C记录下来了,遍历B的时候newLastBaseUpdate !== null,同样会被记录。换句话说,也就是当某一个更新的lane不符合时,后续所有的更新都会被单独记录下来。

另外一种情况是lane符合时,此时会进行执行update,并获取最新的state

javascript
newState = getStateFromUpdate(...)
newState = getStateFromUpdate(...)

getStateFromUpdate方法会根据updatetagpayload获取新的state

随后,将执行了的update存放到effects中,以便更新完成时触发其回调。

javascript
if (effects === null) {
  queue.effects = [update];
} else {
 effects.push(update);
}
if (effects === null) {
  queue.effects = [update];
} else {
 effects.push(update);
}

最后,更新状态后,将对应的属性更新:

javascript
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
workInProgress.memoizedState = newState;
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
workInProgress.memoizedState = newState;

注意此时的newState代表的是执行了所有可执行updatestate,如执行了A,B这两个更新。而baseState表示第一次lane不符合时,前面的state,如遇到C时,lane不符合,baseState记录的是A更新后的state

这样的话,在下一轮更新时,由于lastBaseUpdate存在:

javascript
if (lastBaseUpdate === null) {
  firstBaseUpdate = firstPendingUpdate;
} else {
  lastBaseUpdate.next = firstPendingUpdate;
}
if (lastBaseUpdate === null) {
  firstBaseUpdate = firstPendingUpdate;
} else {
  lastBaseUpdate.next = firstPendingUpdate;
}

此时会将下一轮的更新于当前轮次跳过的更新合并在一次执行,也就是下一轮在执行更新前,会执行本轮跳过的更新,如B,C,D这个三个更新。这也是为什么baseState只记录A更新的原因了,因为它可以作为下一轮更新的起始state

commitUpdateQueue

commitUpdateQueue比较简单,就是执行已经被执行了的updatecallback

javascript
for (let i = 0;i < effects.length;i++) {
  const effect = effects[i];
  const callback = effect.callback;
  if (callback !== null) {
    effect.callback = null;
    callCallback(callback, instance);
  }
}
for (let i = 0;i < effects.length;i++) {
  const effect = effects[i];
  const callback = effect.callback;
  if (callback !== null) {
    effect.callback = null;
    callCallback(callback, instance);
  }
}

FunctionComponent

函数式组件的updateQueueclass组件的差异比较大。它的updateQueue主要用于存放生命周期的回调函数。找到ReactFiberHooks.new.js文件的pushEffect方法,首先会创建一个effect

javascript
const effect: Effect = {
  // 表示 hook 的 tag
  tag,
  // 创建时的回调函数
  create,
  // 销毁时的回调函数
  destroy,
  // hook 的依赖
  deps,
  // 指向下一个 effect
  next: (null: any),
};
const effect: Effect = {
  // 表示 hook 的 tag
  tag,
  // 创建时的回调函数
  create,
  // 销毁时的回调函数
  destroy,
  // hook 的依赖
  deps,
  // 指向下一个 effect
  next: (null: any),
};

随后将该effect形成循环链表(该链表形式与class组件类似),放入到updateQueuelastEffect属性当中:

javascript
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
currentlyRenderingFiber.updateQueue.lastEffect = effect;
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
currentlyRenderingFiber.updateQueue.lastEffect = effect;

HostComponent

HostComponentupdateQueue主要记录props的变化。

javascript
const updatePayload = prepareUpdate(
  instance,
  type,
  oldProps,
  newProps,
  rootContainerInstance,
  currentHostContext,
);
workInProgress.updateQueue = (updatePayload: any);
const updatePayload = prepareUpdate(
  instance,
  type,
  oldProps,
  newProps,
  rootContainerInstance,
  currentHostContext,
);
workInProgress.updateQueue = (updatePayload: any);

prepareUpdate方法在react-dom包中的ReactDOMHostConfig.js文件中定义:

javascript
return diffProperties(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
  );
return diffProperties(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
  );

它主要是调用diffProperties方法,将新旧props进行对比,最后将改变了的属性记录成数组形式。其中偶数index为键,奇数index为值,结构大致如下:

javascript
['name', '张三', 'id', 333, 'style', { color: 'red' }]
['name', '张三', 'id', 333, 'style', { color: 'red' }]