React16源码: React中的update和updateQueue的源码实现

发布时间:2024年01月09日

React中的update和updateQueue


1 )概述

  • ReactDOM.render 过程中,还需要创建一个 update 对象
  • update 用于记录组件状态的改变的一个对象,它存放于Fiber对象的 updateQueue
  • updateQueue,它是一个单向链表的结构,一次整体的更新过程当中
  • 可能在这个queue里会存在多 Update
  • 在这次更新的过程当中,会根据这些 update 的结果算出最终的一个新的state的节果
  • 多个 update 可以同时存在,比如说我们有一个事件里面,连续调用了三次 setState
  • 这三次操作产生的是三个 update,并不会每次 setState,就更新一下整个应用
  • 而是会等3个setState执行完, 3个update创建完, 放到 updateQueue 里面,然后再进行更新的操作
  • 这个涉及到后续的调度的过程,以及 batchUpdates 的原理, 这个暂时不管

2 )源码

现在来看下 update 如何去创建以及它的一个数据结构

参考: https://github.com/facebook/react/blob/v16.6.0/packages/react-reconciler/src/ReactFiberReconciler.js

找到 scheduleRootUpdate 函数

function scheduleRootUpdate(
  current: Fiber,
  element: ReactNodeList,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
  if (__DEV__) {
    if (
      ReactCurrentFiber.phase === 'render' &&
      ReactCurrentFiber.current !== null &&
      !didWarnAboutNestedUpdates
    ) {
      didWarnAboutNestedUpdates = true;
      warningWithoutStack(
        false,
        'Render methods should be a pure function of props and state; ' +
          'triggering nested component updates from render is not allowed. ' +
          'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
          'Check the render method of %s.',
        getComponentName(ReactCurrentFiber.current.type) || 'Unknown',
      );
    }
  }

  const update = createUpdate(expirationTime);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    warningWithoutStack(
      typeof callback === 'function',
      'render(...): Expected the last optional `callback` argument to be a ' +
        'function. Instead received: %s.',
      callback,
    );
    update.callback = callback;
  }
  enqueueUpdate(current, update);

  scheduleWork(current, expirationTime);
  return expirationTime;
}
  • 可见,update的创建过程, const update = createUpdate(expirationTime);, 进入到 createUpdate函数
    • import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
    • 找到 ReactUpdateQueue.js 文件,定位 createUpdate 函数
      export function createUpdate(expirationTime: ExpirationTime): Update<*> {
        return {
          // 对应这一次创建的更新的过期时间, 
          expirationTime: expirationTime,
      
          // tag 对应4种情况
          // export const UpdateState = 0; // 更新 state
          // export const ReplaceState = 1; // 替代 state
          // export const ForceUpdate = 2; // 强制更新
          // export const CaptureUpdate = 3; // 渲染过程中,如果有错误被捕获,会生成一个 update, 让我们重新渲染节点的方式, Error Boundary 在组件内部捕获渲染的错误的一个更新
          // 指定更新的类型,值为以上几种:0, 1, 2, 3
          tag: UpdateState,
          // 更新内容, 对应实际执行的操作内容,比如,在上述 createUpdate中的 update.payload = {element}; 这里是把整个传进去的App, ReactElement 及其整棵树 渲染到 DomRoot 里面
          // 初始渲染的 payload 就如上述
          // 如果是更新,比如 setState 接收的第一个参数,可能是一个对象或方法作为 payload
          payload: null,
          callback: null,
          
          // next 对应下一个 update, 因为,update 都是存放在 updateQueue 中的, 而 updateQueue 是一个单项列表的结构
          // 每个 update 都有一个 next, 在 UpdateQueue 中有一个 firstUpdate 和 lastUpdate 记录的是单项列表的开头和结尾
          // 从开头到结尾都是通过 next 串联起来的,把整个update 单链表的结构连接起来
          // 通过拿到 firstUpdate 基于next一层层找到 lastUpdate
          next: null,
          // 指向下一个 side effect
          nextEffect: null,
        };
      }
      
    • 同样定位到 UpdateQueue 里面
      export type UpdateQueue<State> = {
        // 每次更新应用渲染完成后,调用 UpdateQueue 计算出一个新的state 存放在 baseState 上
        // 下一次节点有更新,产生了一个更新的队列,在计算新的state时,会在 baseState 基础上计算
        baseState: State,
      
        // firstUpdate 和 lastUpdate 记录单项链表的数据结构
        // 队列中的第一个 Update
        firstUpdate: Update<State> | null,
        // 队列中的最后一个 Update
        lastUpdate: Update<State> | null,
      
        // 下面两个也是记录单项链表的数据结构,只不过对应有错误捕获的时候的 Update
        firstCapturedUpdate: Update<State> | null,
        lastCapturedUpdate: Update<State> | null,
      
        // 第一个 side effect
        firstEffect: Update<State> | null,
        // 最后一个 side effect
        lastEffect: Update<State> | null,
      
        // 第一个和最后一个 捕获产生的 side effect
        firstCapturedEffect: Update<State> | null,
        lastCapturedEffect: Update<State> | null,
      };
      
    • update 和 updateQueue 数据结构相对来说是比较简单的
    • createUpdate 内部要调用一个 enqueueUpdate, 如果在 setState 和 forceUpdate 里面的操作
    • 去创建update时,需要调用 enqueueUpdate ,进入这个函数
      export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
        // Update queues are created lazily.
        const alternate = fiber.alternate;
        let queue1;
        let queue2;
        if (alternate === null) {
          // There's only one fiber.
          queue1 = fiber.updateQueue;
          queue2 = null;
          if (queue1 === null) {
            queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
          }
        } else {
          // There are two owners.
          queue1 = fiber.updateQueue;
          queue2 = alternate.updateQueue;
          if (queue1 === null) {
            if (queue2 === null) {
              // Neither fiber has an update queue. Create new ones.
              queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
              queue2 = alternate.updateQueue = createUpdateQueue(
                alternate.memoizedState,
              );
            } else {
              // Only one fiber has an update queue. Clone to create a new one.
              queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
            }
          } else {
            if (queue2 === null) {
              // Only one fiber has an update queue. Clone to create a new one.
              queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
            } else {
              // Both owners have an update queue.
            }
          }
        }
        if (queue2 === null || queue1 === queue2) {
          // There's only a single queue.
          appendUpdateToQueue(queue1, update);
        } else {
          // There are two queues. We need to append the update to both queues,
          // while accounting for the persistent structure of the list — we don't
          // want the same update to be added multiple times.
          if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
            // One of the queues is not empty. We must add the update to both queues.
            appendUpdateToQueue(queue1, update);
            appendUpdateToQueue(queue2, update);
          } else {
            // Both queues are non-empty. The last update is the same in both lists,
            // because of structural sharing. So, only append to one of the lists.
            appendUpdateToQueue(queue1, update);
            // But we still need to update the `lastUpdate` pointer of queue2.
            queue2.lastUpdate = update;
          }
        }
      
        if (__DEV__) {
          if (
            fiber.tag === ClassComponent &&
            (currentlyProcessingQueue === queue1 ||
              (queue2 !== null && currentlyProcessingQueue === queue2)) &&
            !didWarnUpdateInsideUpdate
          ) {
            warningWithoutStack(
              false,
              'An update (setState, replaceState, or forceUpdate) was scheduled ' +
                'from inside an update function. Update functions should be pure, ' +
                'with zero side-effects. Consider using componentDidUpdate or a ' +
                'callback.',
            );
            didWarnUpdateInsideUpdate = true;
          }
        }
      }
      
      • const alternate = fiber.alternate; 先读取 alternate,这个就是 current 到 workInProgress 的映射关系
      • 在这里就要确保 current 和 workInProgress 对应的 updateQueue 是相同的
      • 在这里会统一被处理,在这里,参数 fiber 是 current, alternate 是 workInProgress
      • 先判断 alternate 是否存在, 不存在处理 queue1 和 queue2, 进入 createUpdateQueue
        export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
          const queue: UpdateQueue<State> = {
            baseState,
            firstUpdate: null,
            lastUpdate: null,
            firstCapturedUpdate: null,
            lastCapturedUpdate: null,
            firstEffect: null,
            lastEffect: null,
            firstCapturedEffect: null,
            lastCapturedEffect: null,
          };
          return queue;
        }
        
      • 生成的 UpdateQueue 就是之前看过的这个数据结构
      • 接着往下的判断中进入 appendUpdateToQueue
        function appendUpdateToQueue<State>(
          queue: UpdateQueue<State>,
          update: Update<State>,
        ) {
          // Append the update to the end of the list.
          if (queue.lastUpdate === null) {
            // Queue is empty
            queue.firstUpdate = queue.lastUpdate = update;
          } else {
            queue.lastUpdate.next = update; // 当前最后(后面会变成倒数第二位元素)的next指向update
            queue.lastUpdate = update; // queue的lastUpdate 指向 update
          }
        }
        
        • 如果 lastUpdate 不存在,则说明 Queue 是空的,则进行处理
        • 如果存在,进行继续如上 enqueueUpdate中的 if else 的处理
      • 接着往下 都是这类逻辑,具体逻辑都在上面 enqueueUpdate 源码中
      • 总体来讲是初始化 Fiber 上面的 updateQueue, 以及进行更新
    • 它接收的两个参数,Fiber 和 Update 对象
      • Fiber 对象是在 创建 update时用到的Fiber
      • 对应 ReactDOM.render 就是我们的 RootFiber
      • 就是我们 FiberRootcurrent
    • 在调用 scheduleRootUpdate 函数的 updateContainerAtExpirationTime 函数中
      export function updateContainerAtExpirationTime(
        element: ReactNodeList,
        container: OpaqueRoot,
        parentComponent: ?React$Component<any, any>,
        expirationTime: ExpirationTime,
        callback: ?Function,
      ) {
        // TODO: If this is a nested container, this won't be the root.
        const current = container.current;
      
        if (__DEV__) {
          if (ReactFiberInstrumentation.debugTool) {
            if (current.alternate === null) {
              ReactFiberInstrumentation.debugTool.onMountContainer(container);
            } else if (element === null) {
              ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
            } else {
              ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
            }
          }
        }
      
        const context = getContextForSubtree(parentComponent);
        if (container.context === null) {
          container.context = context;
        } else {
          container.pendingContext = context;
        }
      
        return scheduleRootUpdate(current, element, expirationTime, callback);
      }
      
      • const current = container.current; container.current 就对应一个 Fiber 对象
文章来源:https://blog.csdn.net/Tyro_java/article/details/135477352
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。