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

这篇具有很好参考价值的文章主要介绍了React16源码: React中的update和updateQueue的源码实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 函数文章来源地址https://www.toymoban.com/news/detail-784486.html

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 对象

到了这里,关于React16源码: React中的update和updateQueue的源码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • React16源码: React中的completeUnitOfWork的源码实现

    completeUnitOfWork 1 )概述 各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段 它是去向下遍历一棵 fiber 树的一侧的子节点,然后遍历到叶子节点为止,以及 return 自己 child 的这种方式 在 performUnitOfWork 里面,还有一个方法叫做 completeUnitOfWork 在

    2024年01月23日
    浏览(32)
  • React16源码: React中的IndeterminateComponent的源码实现

    IndeterminateComponent 1 )概述 这是一个比较特殊的component的类型, 就是还没有被指定类型的component 在一个fibrer被创建的时候,它的tag可能会是 IndeterminateComponent 在 packages/react-reconciler/src/ReactFiber.js 中,有一个方法 createFiberFromTypeAndProps 中,一开始就声明了 在最终调用 createFibe

    2024年01月21日
    浏览(32)
  • React16源码: React中的performWork的源码实现

    performWork 1 )概述 performWork 涉及到在调度完成,或者同步任务进来之后整个 root 节点链条如何更新 怎么更新一棵 Fiber 树,它的每一个节点是如何被遍历到,以及如何进行更新操作 A. 在执行 performWork 时候,是否有 deadline 的区分 deadline 是通过 reactschedule 它的一个时间片,更新

    2024年01月17日
    浏览(25)
  • React16源码: React中的beginWork的源码实现

    beginWork 1 )概述 在 renderRoot 之后,要对我们的 Fiber 树每一个节点进行对应的更新 更新节点的一个入口方法,就是 beginWork 这个入口方法会有帮助我们去优化整棵树的更新过程 react 它的节点其实是非常多的,如果每一次子节点的一个更新 就需要每一个节点都执行一遍更新的话

    2024年01月20日
    浏览(35)
  • React16源码: React中的reconcileChildren的源码实现

    reconcileChildren 1 )概述 在更新了一个节点之后,拿到它的props.children 要根据这个children里面的 ReactElement 来去创建子树的所有的 fiber 对象 要根据 props.children 来生成 fiber 子树,然后判断 fiber 对象它是否是可以复用的 因为我们在第一次渲染的时候,就已经渲染了整个 fiber 子树

    2024年01月20日
    浏览(30)
  • React16源码: React中的setState和forceUpdate源码实现

    setState 和 forceUpdate 1 ) 概述 通过 class component 内部的 setState ,以及 forceUpdate 去更新一个组件的过程 在react的应用当中,我们只有 ReactDOM.render setState ,以及 forceUpdate 这几种种方式去更新react的应用是合理的,其他没有什么特别常用的方式去更新了 而且react官方推荐的也是用

    2024年01月25日
    浏览(28)
  • React16源码: React中的不同的expirationTime的源码实现

    不同的 expirationTime 1 )概述 在React中不仅仅有异步任务 大部分情况下都是同步的任务,所以会有不同 expirationTime 的存在 2 )种类 A. Sync 模式,优先级最高 任务创建完成之后,立马更新到真正的dom里面 是一个创建即更新的流程 B. Async 模式, 异步模式 会有一个调度 包含一系列

    2024年02月01日
    浏览(26)
  • React16源码: React中的reconcileChildIterator和reconcileChildrenArray的源码实现

    reconcileChildIterator 和 reconcileChildrenArray 1 )概述 在react更新某一个节点的时候,要根据这个节点,它的类型去获取它的children 比如说如果是 Function Component,它要调用这个 component 计算出它的return的属性 return的属性可能是一个数组,可能是单个的 ReactElement,可能是 number, string

    2024年01月20日
    浏览(25)
  • React16源码: React中的PortalComponent创建, 调和, 更新的源码实现

    PortalComponent 1 )概述 React Portal之所以叫Portal,因为做的就是和“传送门”一样的事情 render到一个组件里面去,实际改变的是网页上另一处的DOM结构 主要关注 portal的创建, 调和, 更新过程 2 )源码 定位到 packages/react-dom/src/client/ReactDOM.js#L576 这里调用的是 ReactPortal.createPortal ,

    2024年01月21日
    浏览(29)
  • React16源码: React中的completeWork对HostComponent处理的源码实现

    HostComponent 1 )概述 在 completeWork 当中,我们需要对 HostComponent 的一些操作有哪些? 首先在一次更新而不是初次渲染的情况下 需要去 diffProperties 来计算,需要更新的内容 也就是在 vdom 中去进行一个对比来判断这一个节点是否需要真的去更新它 以此来最低程度的去更新整个 d

    2024年01月23日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包