首页 > 其他分享 >updateContainer

updateContainer

时间:2024-05-13 23:41:32浏览次数:32  
标签:fiber return update 更新 React updateContainer var

updateContainer 是 React 源码中负责更新容器的函数之一。初始化更新时调用就是该方法。这个函数位于 React 源码中的 ReactFiberReconciler.js 文件中,下面是对 updateContainer 函数的分析:

function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
    ... element是render方法的第一个入参,也就是入口模块(常见<App />写法)
        containers是createRoot方法的入参(最终挂载的容器),然后创建出来的FiberRoot,
        在createRoot方法做了生成FiberRoot、委托事件到容器上。
        最终返回一个赛道值
}
// 类型
type ReactNodeList =
  | React$Element<any>
  | ReactPortal
  | ReactText
  | ReactFragment
  | ReactProvider<any>
  | ReactConsumer<any>;

type OpaqueRoot = FiberRoot 

updateContainer 函数接受四个参数:

  • element:要在容器中呈现的 React 元素(render方法的第一个参数,是必填参数。也就是入口模块(常见<App />写法))
  • container:React 容器,通常是应用程序的根节点(必填入参。是createRoot方法的入参(最终挂载的容器),然后创建出来的FiberRoot)
  • parentComponent:可选参数,父组件,表示此容器的父组件。
  • callback:可选参数,更新容器后要执行的回调函数。
if (__DEV__) {
    // 开发阶段相关,不需要关心
    onScheduleRoot(container, element);
}
// 这里根据不同的容器类型(如 DOM、React Native 等),获取相应的更新器
const current = container.current; 
// 计算当前时间(后面有讲方法内部的执行)
const eventTime = requestEventTime();
// 计算当前更新的赛道
const lane = requestUpdateLane(current);

if (enableSchedulingProfiler) {
    // 有关性能追踪的,不需要看
   markRenderScheduled(lane);
}

// getContextForSubtree函数根据父组件计算子树的上下文(后面有例子说明)
const context = getContextForSubtree(parentComponent);
// 一旦应用或组件树中使用了Context API,并且已经对容器进行了首次渲染或上下文更新处理,container.context将不再为null。
if (container.context === null) {
   container.context = context;
} else {
   container.pendingContext = context;
}

if (__DEV__) {
// 开发阶段相关,不需要关心
 if (
      ReactCurrentFiberIsRendering &&
      ReactCurrentFiberCurrent !== null &&
      !didWarnAboutNestedUpdates
    ) {
      didWarnAboutNestedUpdates = true;
    // 检测是否在渲染过程中出现了嵌套更新,如果出现了嵌套更新,则给出警告。比如在render方法中更新state,又或者在componentDidUpdatef更新state,造成无限更新,陷入死循环
      console.error(
        '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.',
        getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown',
      );
    }
}

// 创建了一个更新对象 update,用于描述更新的内容,然后将要渲染的元素作为 payload 存入更新对象中,并将回调函数也存入更新对象中,例如,在初始化渲染,描述的更新内容就是将element渲染到页面
const update = createUpdate(eventTime, lane);
// Caution: React DevTools currently depends on this property being called "element".
// 译:React开发者工具依赖element属性
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
    if (__DEV__) {
     // 开发阶段相关,不需要关心
      if (typeof callback !== 'function') {
        console.error(
          'render(...): Expected the last optional `callback` argument to be a ' +
            'function. Instead received: %s.',
          callback,
        );
      }
    }
    update.callback = callback;
}

// enqueueUpdate是负责将上面的更新对象update添加到当前的容器的更新队列上(后面会有讲解)
const root = enqueueUpdate(current, update, lane);
if (root !== null) {
    // 执行scheduleUpdateOnFiber,负责安排 Fiber 对象的更新
    scheduleUpdateOnFiber(root, current, lane, eventTime);
    // 执行entangleTransitions,处理useTransition的,后面有安排讲解
    entangleTransitions(root, current, lane);
}
  // 最终返回一个
  return lane;

eventTime: 记录每个更新的时间,在整个更新阶段,几乎都能看待eventTime的影子。它最终会被记录在更新对象中,是用来协调React的渲染和更新优先级的,确保可以按照正确的顺序和优先级执行。

requestEventTime: 调用performance.now()获取当前时间(毫秒级),并缓存,下次更新的时候如果有缓存,取缓存值。代表是同一批次,相同赛道等级的更新

var currentEventTime = -1 /// ..全局状态
/// ...
function requestEventTime() {
    if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
      // 执行这里一般是初始化渲染,首次更新
      // We're inside React, so it's fine to read the actual time.
      // 译:在React内部,读区当前时间
      return now();
    }
    
    // We're not inside React, so we may be in the middle of a browser event.
    // 译:如果当前不在React的控制流程中,可能处于浏览器的事件处理中
    if (currentEventTime !== NoTimestamp) {
      // 执行这里一般是多次同步执行更新状态,例如多次同步执行setState
      // Use the same start time for all updates until we enter React again.
      // 译:直到开始一次新的更新前,所有更新使用相同的时间。
      return currentEventTime;
    } 

   // This is the first update since React yielded. Compute a new start time
    currentEventTime = now(); /// now = performance.now
    return currentEventTime;
}

 这里说明下要注意的几点:

  • executionContext: 表示render阶段的执行上下文
  • CommitContext: 表示commit阶段执行上下文
  • currentEventTime: 是一个全局状态,默认值-1

这里缓存currentEventTime,一个重要的原因是在一个更新周期内,很多状态更新可能几乎同时发生(例如多次同步调用setState)。如果每个更新都记录一个独立的时间戳,这会导致同一周期内的不同更新事件有时间差异,从而可能引发问题,尤其是在并发处理中,协调React的渲染和更新优先级,确保正确的顺序和优先级执行都是通过requestEventTime计算保证的。

Lane:更新赛道应该在React中很重要的一个概念来,具体看Lane章节这里只讲方法相关内容

requestUpdateLane:计算更新赛道

function requestUpdateLane(fiber) {
    var mode = fiber.mode;
    if ((mode & ConcurrentMode) === NoMode) {
      // 如果不是并发模式,则同步更新,如果是18版本,不会到这里
      return SyncLane;
    } else if ( (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes) {
       // 可以不用看这里,官方不支持
       // 更新发生在render阶段,并且已经有正在进行的赛道,,则以当前渲染的赛道计算出最高等级赛道并返回
      // This is a render phase update. These are not officially supported. The
      // old behavior is to give this the same "thread" (lanes) as
      // whatever is currently rendering. So if you call `setState` on a component
      // that happens later in the same render, it will flush. Ideally, we want to
      // remove the special case and treat them as if they came from an
      // interleaved event. Regardless, this pattern is not officially supported.
      // This behavior is only a fallback. The flag only exists until we can roll
      // out the setState warning, since existing code might accidentally rely on
      // the current behavior.
      // 译:这是发生在render阶段的更新。官方不支持这种行为。
      // 以前做法是将正在渲染内容的 "线程"(赛道)赋予这个更新。
      // 因此,如果在同一渲染阶段,如果稍晚一点调用 `setState` ,他会刷新。
      // 理想情况下,我们想移除这种情况和修复他们,并将他们视为是交互事件。
      // 但无论如何,官方都不会支持这种做法。这种做法仅作为备用的。该标志只存在到我们可以发出 setState 警告为止、因此可能有代码会无意触发了这这里的动作
      // 根据 workInProgressRootRenderLanes 计算出最高等级的赛道
      return pickArbitraryLane(workInProgressRootRenderLanes); 
  }
  var isTransition = requestCurrentTransition() !== NoTransition;
  if (isTransition) { 
    // 如果没用startTransition,不用关心这里 
    if ( ReactCurrentBatchConfig$3.transition !== null) {
      var transition = ReactCurrentBatchConfig$3.transition; 
      if (!transition._updatedFibers) { 
      transition._updatedFibers = new Set();
     } 
    transition._updatedFibers.add(fiber); 
  } 
  // The algorithm for assigning an update to a lane should be stable for all 
  // updates at the same priority within the same event. To do this, the 
  // inputs to the algorithm must be the same. 
  // 
  // The trick we use is to cache the first of each of these inputs within an 
  // event. Then reset the cached values once we can be sure the event is 
  // over. Our heuristic for that is whenever we enter a concurrent work loop. 
  if (currentEventTransitionLane === NoLane) { 
    // All transitions within the same event are assigned the same lane. 
    currentEventTransitionLane = claimNextTransitionLane(); 
  } 
  return currentEventTransitionLane; 
  } 
  // Updates originating inside certain React methods, like flushSync, have 
  // their priority set by tracking it with a context variable. 
  // The opaque type returned by the host config is internally a lane, so we can 
  // use that directly. 
  // TODO: Move this type conversion to the event priority module. 
  // 译:源自React内部的更新,像flushSync(同步刷新)的优先级是通过用一个环境变量跟踪其设定的.
  // 返回的未知类型是来自内部的赛道,可以直接使用
  // 获取更新赛道(updateLane不为空,代表同一批次的更新)
  var updateLane = getCurrentUpdatePriority(); 
  if (updateLane !== NoLane) { 
    return updateLane; 
  } 
  // This update originated outside React. Ask the host environment for an 
  // appropriate priority, based on the type of event. 
  // 
  // The opaque type returned by the host config is internally a lane, so we can 
  // use that directly. 
  // TODO: Move this type conversion to the event priority module. 
  // 译:源自React外部的更新,要求基于事件类型得到一个合适的优先级
  //
  // 返回的未知类型是内部的赛道,可以直接使用
  var eventLane = getCurrentEventPriority(); 
   return eventLane;
}

 这里还是比较好理解,判断当前环境是否是并发模式,不是则同步更新,然后判断当前是否处于渲染阶段(render阶段),并且是否已经有正进行的更新Fiber,然后返回正在渲染的Fiber最高级赛道,否之,如果有用到过度方案,如useStranisition,就返回该方案对应的赛道,又或者是否使用了内部的方法作为更新方案,那返回对应的赛道,如果上述都不满足,根据当前更新的事件类型返回对应的赛道。

getContextForSubtree 获取子组件树最近的Context。初始化渲染返回的是一个空对象。在React18版本,getContextForSubtree主要服务于一个unstable_renderSubtreeIntoContainer方法,该方法官方已经声明废弃。不过可以跟着unstable_renderSubtreeIntoContainer方法往下学习下getContextForSubtree是如何获取最近的Context的

function getContextForSubtree(parentComponent) {
    if (!parentComponent) {
      // 如果夫组件不存在,返回一个空对象
      return emptyContextObject;
    }

    var fiber = get(parentComponent); // 获取父组件的Fiber

    // 找到最近的Context,
    var parentContext = findCurrentUnmaskedContext(fiber);

    if (fiber.tag === ClassComponent) {
      var Component = fiber.type;

      if (isContextProvider(Component)) {
        return processChildContext(fiber, Component, parentContext);
      }
    }

    return parentContext;
}

// findCurrentUnmaskedContext 方法就是从当前节点向上查找到最近的Context并返回
function findCurrentUnmaskedContext(fiber) {
    {
      if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) {
        throw new Error('...');
      }

      var node = fiber;

      do {
        switch (node.tag) {
          case HostRoot:
            return node.stateNode.context;

          case ClassComponent:
            {
              var Component = node.type;

              if (isContextProvider(Component)) {
                return node.stateNode.__reactInternalMemoizedMergedChildContext;
              }

              break;
            }
        }

        node = node.return;
      } while (node !== null);

      throw new Error('...');
    }
}

/// unstable_renderSubtreeIntoContainer方法最终调的是legacyRenderSubtreeIntoContainer
/// 这里只讨论parentComponent, children, container,分别是夫组件,要渲染的组件和容器
/// 到这里可以知道,把夫组件传递进来主要是为了子组件可以从夫组件拿到Context
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
    var maybeRoot = container._reactRootContainer;
    var root;

    if (!maybeRoot) {
      // Initial mount
      root = legacyCreateRootFromDOMContainer(container, children, parentComponent, callback, forceHydrate);
    } else {
      root = maybeRoot;

      if (typeof callback === 'function') {
        var originalCallback = callback;

        callback = function () {
          var instance = getPublicRootInstance(root);
          originalCallback.call(instance);
        };
      } // Update


      updateContainer(children, root, parentComponent, callback); // 
    }

    return getPublicRootInstance(root);
}

createUpdate 没什么好讲的,就是缓存了eventTime和Lane,然后返回一个对象

enqueueUpdate 用于将更新入队到Fiber节点的函数,是非常重要的方法。

enqueueUpdate<State>(
  fiber: Fiber,
  update: Update<State>,
  lane: Lane,
): FiberRoot | null {
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
    // Only occurs if the fiber has been unmounted.
    // 译:卸载了为null
    return null;
  }

  // Fiber上的共享更新队列,存储所有即将处理的更新
  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
 
  if (__DEV__) {
   // 开发阶段的警报,可以不要关心
   // 为了防止更新函数的二次更新,如在setSate的第二个函数入参中再次更新状态,这是官方不推荐的
    if (
      currentlyProcessingQueue === sharedQueue &&
      !didWarnUpdateInsideUpdate
    ) {
      console.error(
        '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;
    }
  }

  if (isUnsafeClassRenderPhaseUpdate(fiber)) {
    // 如果是在render阶段又重新更新状态,会进入到这里来
    // 这里isUnsafeClassRenderPhaseUpdate比较简单,就一行代码,判断是否处于render阶段上下文
    // 通常这种不安全的更新指在UNSAFE_componentWillMount或者在render钩子函数更新状态,又或者凡事在render阶段前执行的钩子函数中更新了状态,这类都属于不安全更新
    // This is an unsafe render phase update. Add directly to the update
    // queue so we can process it immediately during the current render.
    // 译:这是不安全渲染阶段的更新。在渲染期间,直接添加到更新队列,然后立即执行
    const pending = sharedQueue.pending;
    if (pending === null) {
      // This is the first update. Create a circular list.
      update.next = update;
    } else {
      update.next = pending.next;
      pending.next = update;
    }

   // 最终update会是一个链环数据结构
    sharedQueue.pending = update;

    // Update the childLanes even though we're most likely already rendering
    // this fiber. This is for backwards compatibility in the case where you
    // update a different component during render phase than the one that is
    // currently renderings (a pattern that is accompanied by a warning).
    return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
  } else {
    return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
  }
}

var unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot;
function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {
    var interleaved = queue.interleaved;

    // 同在上面的不安全更新一样,同样把更新的链成一个链环的数据结构(图2所示)
    if (interleaved === null) {
      // This is the first update. Create a circular list.
      update.next = update; // At the end of the current render, this queue's interleaved updates will
      // be transferred to the pending queue.

      // 这里只需要知道把queue存到一个全局的队列中即可
      pushConcurrentUpdateQueue(queue);
    } else {
      update.next = interleaved.next;
      interleaved.next = update;
    }

    queue.interleaved = update;
    return markUpdateLaneFromFiberToRoot(fiber, lane);
}
// 可以看到,enqueueConcurrentClassUpdate最后也是返回了markUpdateLaneFromFiberToRoot的结果
// markUpdateLaneFromFiberToRoot就比较好理解,就是从当前fiber节点开始,合并优先级,直到顶层节点,然后返回容器节点
function markUpdateLaneFromFiberToRoot(sourceFiber, lane) {
    // Update the source fiber's lanes
    sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
    var alternate = sourceFiber.alternate;

    if (alternate !== null) {
      alternate.lanes = mergeLanes(alternate.lanes, lane);
    }

    {
      if (alternate === null && (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags) {
        warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
      }
    } // Walk the parent path to the root and update the child lanes.


    var node = sourceFiber;
    var parent = sourceFiber.return;

    while (parent !== null) {
      parent.childLanes = mergeLanes(parent.childLanes, lane);
      alternate = parent.alternate;

      if (alternate !== null) {
        alternate.childLanes = mergeLanes(alternate.childLanes, lane);
      } else {
        {
          if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
            warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
          }
        }
      }

      node = parent;
      parent = parent.return;
    }

    if (node.tag === HostRoot) {
      var root = node.stateNode;
      return root;
    } else {
      return null;
    }
 }

 到这里算是updateContainer方法及其相关的方法都已经结束,后面就是开始执行scheduleUpdateOnFiberentangleTransitions,进入下一个阶段

标签:fiber,return,update,更新,React,updateContainer,var
From: https://www.cnblogs.com/goather/p/18187313

相关文章