React16源码: React中LegacyContext的源码实现

news2024/11/15 17:27:32

LegacyContext

  • 老的 contextAPI 也就是我们使用 childContextTypes 这种声明方式
  • 来从父节点为它的子树提供 context 内容的这么一种方式
  • 遗留的contextAPI 在 react 17 被彻底移除了,就无法使用了
  • 那么为什么要彻底移除这个contextAPI的使用方式呢?
  • 因为它对性能的影响会比较的大,它会影响整个子树的一个更新过程
  • 它嵌套的context提供者是需要进行一个数据的合并的
    • 嵌套组件中,如果父子两者都提供相同的变量,会进行一个合并
    • 越接近里层,越会被选择
    • 也就是说在孙子消费变量的时候,选择父亲的,舍弃爷爷的

2 )源码

定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L1522

在这里,有这个判断 if ( oldProps === newProps && !hasLegacyContextChanged() && updateExpirationTime < renderExpirationTime ) {}

看到有

import {
  hasContextChanged as hasLegacyContextChanged
} from './ReactFiberContext';

关注 hasLegacyContextChanged 基于此,定位到 packages/react-reconciler/src/ReactFiberContext.js#L115

// 要去推入 stack 的值的时候,就要去创建这么一个 cursor 来标记不同类型的一个值
function hasContextChanged(): boolean {
  return didPerformWorkStackCursor.current;
}

回顾到 context-stack 中

// packages/react-reconciler/src/ReactFiberContext.js#L36

// A cursor to the current merged context object on the stack.
// 用来记录我们当前的我们更新到某一个节点之后,它应该可以拿到的context对应的所有值
let contextStackCursor: StackCursor<Object> = createCursor(emptyContextObject);

// A cursor to a boolean indicating whether the context has changed.
// 代表着我们在更新到某一个节点的时候,它这个context是否有变化这么一个情况
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);

再次回到 ReactFiberBeginWork.js 中的 if ( oldProps === newProps && !hasLegacyContextChanged() && updateExpirationTime < renderExpirationTime ) {}

对于在 beginWork 中的 context 操作,可以看上述判断成立的条件下的代码,即便有符合跳过更新的操作,依然 需要 push 和 pop 操作

进入代码

switch (workInProgress.tag) {
  case ClassComponent: {
    const Component = workInProgress.type;
    // 如果当前组件是一个 provider 则进行 push 操作
    if (isLegacyContextProvider(Component)) {
      pushLegacyContextProvider(workInProgress);
    }
    break;
  }
}
  • 跟遗留的 contextAPI 有关,通过 legency 标志,如果一个组件能够作为一个 context 的提供者

  • 那么它肯定是一个 ClassComponent, 因为要通过 getchildcontext 这么一个方法来声明我们子树当中提供了哪些 concontext

  • 最主要的就是来看在classcomponent的更新过程当中,如果它是一个contextprovider,那么它要执行的操作是 pushLegacyContextProvider

  • 进入 isLegacyContextProvider, 看到它是 isContextProvider 的别名

    // 这个 type 就是组件实例,这个 childContextTypes
    function isContextProvider(type: Function): boolean {
      // 通过判断应用中声明的 class 上面是否有这个属性
      const childContextTypes = type.childContextTypes;
      return childContextTypes !== null && childContextTypes !== undefined;
    }
    
    • 通过声明的这个class给它挂载 childContextTypes 来表示它是一个context的提供者
    • 只有声明了这个之后,它才会作为一个context的provider来提供子树上面的context
    • 如果不这么声明,即便在class里面提供了 getChildContext 这个方法,还是拿不到对应的context
  • 进入 pushLegacyContextProvider 它是 pushContextProvider 的别名

    function pushContextProvider(workInProgress: Fiber): boolean {
      const instance = workInProgress.stateNode;
      // We push the context as early as possible to ensure stack integrity.
      // If the instance does not exist yet, we will push null at first,
      // and replace it on the stack later when invalidating the context.
      const memoizedMergedChildContext =
        (instance && instance.__reactInternalMemoizedMergedChildContext) ||
        emptyContextObject;
    
      // Remember the parent context so we can merge with it later.
      // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
      previousContext = contextStackCursor.current; // 获取之前的 context 挂载到全局变量上
      push(contextStackCursor, memoizedMergedChildContext, workInProgress);
      push(
        didPerformWorkStackCursor,
        didPerformWorkStackCursor.current,
        workInProgress,
      );
    
      return true;
    }
    

以上是可以跳出当前组件的更新的一个处理情况

如果我们可以跳出组件的更新,也就是代表着当前这个 classComponent,它的state它的props都应该是没有任何变化的

这个时候, 当然是可以直接使用保存在它上面原始的 context 的对象

如果它是一个需要更新的 classComponent,需要看一下 updateClassComponent 这个更新方法

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  // Push context providers early to prevent context stack mismatches.
  // During mounting we don't know the child context yet as the instance doesn't exist.
  // We will invalidate the child context in finishClassComponent() right after rendering.
  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  prepareToReadContext(workInProgress, renderExpirationTime);

  const instance = workInProgress.stateNode;
  let shouldUpdate;
  if (instance === null) {
    if (current !== null) {
      // An class component without an instance only mounts if it suspended
      // inside a non- concurrent tree, in an inconsistent state. We want to
      // tree it like a new mount, even though an empty version of it already
      // committed. Disconnect the alternate pointers.
      current.alternate = null;
      workInProgress.alternate = null;
      // Since this is conceptually a new fiber, schedule a Placement effect
      workInProgress.effectTag |= Placement;
    }
    // In the initial pass we might need to construct the instance.
    constructClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    mountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    shouldUpdate = true;
  } else if (current === null) {
    // In a resume, we'll already have an instance we can reuse.
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  return finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
}
  • 一进来就调用了 isLegacyContextProvider 方法

    • 就是如果它是一个contextprovider,那么它要进行 push 操作 pushLegacyContextProvider
  • 接下去, 它调用了一个方法,叫做 prepareToReadContext 这么一个方法

    • 这个方法和新的contextAPI有关,先跳过
  • 接下去基本上没有跟 context 相关的内容了,这里进入 finishClassComponent

    function finishClassComponent(
      current: Fiber | null,
      workInProgress: Fiber,
      Component: any,
      shouldUpdate: boolean,
      hasContext: boolean,
      renderExpirationTime: ExpirationTime,
    ) {
      // Refs should update even if shouldComponentUpdate returns false
      markRef(current, workInProgress);
    
      const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
    
      if (!shouldUpdate && !didCaptureError) {
        // Context providers should defer to sCU for rendering
        if (hasContext) {
          invalidateContextProvider(workInProgress, Component, false);
        }
    
        return bailoutOnAlreadyFinishedWork(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
    
      const instance = workInProgress.stateNode;
    
      // Rerender
      ReactCurrentOwner.current = workInProgress;
      let nextChildren;
      if (
        didCaptureError &&
        typeof Component.getDerivedStateFromError !== 'function'
      ) {
        // If we captured an error, but getDerivedStateFrom catch is not defined,
        // unmount all the children. componentDidCatch will schedule an update to
        // re-render a fallback. This is temporary until we migrate everyone to
        // the new API.
        // TODO: Warn in a future release.
        nextChildren = null;
    
        if (enableProfilerTimer) {
          stopProfilerTimerIfRunning(workInProgress);
        }
      } else {
        if (__DEV__) {
          ReactCurrentFiber.setCurrentPhase('render');
          nextChildren = instance.render();
          if (
            debugRenderPhaseSideEffects ||
            (debugRenderPhaseSideEffectsForStrictMode &&
              workInProgress.mode & StrictMode)
          ) {
            instance.render();
          }
          ReactCurrentFiber.setCurrentPhase(null);
        } else {
          nextChildren = instance.render();
        }
      }
    
      // React DevTools reads this flag.
      workInProgress.effectTag |= PerformedWork;
      if (current !== null && didCaptureError) {
        // If we're recovering from an error, reconcile without reusing any of
        // the existing children. Conceptually, the normal children and the children
        // that are shown on error are two different sets, so we shouldn't reuse
        // normal children even if their identities match.
        forceUnmountCurrentAndReconcile(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
      } else {
        reconcileChildren(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
      }
    
      // Memoize state using the values we just used to render.
      // TODO: Restructure so we never read values from the instance.
      workInProgress.memoizedState = instance.state;
    
      // The context might have changed so we need to recalculate it.
      if (hasContext) {
        invalidateContextProvider(workInProgress, Component, true);
      }
    
      return workInProgress.child;
    }
    
    • hasContext 是否是一个 contextProvider
    • 如果是 true, 则执行 invalidateContextProvider(workInProgress, Component, false);
      function invalidateContextProvider(
        workInProgress: Fiber,
        type: any,
        didChange: boolean,
      ): void {
        const instance = workInProgress.stateNode;
        invariant(
          instance,
          'Expected to have an instance by this point. ' +
            'This error is likely caused by a bug in React. Please file an issue.',
        );
      
        // 如果有变化
        if (didChange) {
          // Merge parent and own context.
          // Skip this if we're not updating due to sCU.
          // This avoids unnecessarily recomputing memoized values.
          const mergedContext = processChildContext(
            workInProgress,
            type,
            previousContext,
          );
          instance.__reactInternalMemoizedMergedChildContext = mergedContext;
      
          // Replace the old (or empty) context with the new one.
          // It is important to unwind the context in the reverse order.
          pop(didPerformWorkStackCursor, workInProgress);
          pop(contextStackCursor, workInProgress);
          // Now push the new context and mark that it has changed.
          push(contextStackCursor, mergedContext, workInProgress);
          push(didPerformWorkStackCursor, didChange, workInProgress);
        } else {
          pop(didPerformWorkStackCursor, workInProgress);
          push(didPerformWorkStackCursor, didChange, workInProgress);
        }
      }
      
      • 通过 processChildContext 计算出新的 context 并挂载到 __reactInternalMemoizedMergedChildContext
      • 之后,pop 2次,push 2次 来处理了栈内的顺序
      • 进入 processChildContext 看下这个方法
        function processChildContext(
          fiber: Fiber,
          type: any,
          parentContext: Object,
        ): Object {
          const instance = fiber.stateNode;
          const childContextTypes = type.childContextTypes;
        
          // TODO (bvaughn) Replace this behavior with an invariant() in the future.
          // It has only been added in Fiber to match the (unintentional) behavior in Stack.
          // 这个属性一定是 function 才能生效
          if (typeof instance.getChildContext !== 'function') {
            if (__DEV__) {
              const componentName = getComponentName(type) || 'Unknown';
        
              if (!warnedAboutMissingGetChildContext[componentName]) {
                warnedAboutMissingGetChildContext[componentName] = true;
                warningWithoutStack(
                  false,
                  '%s.childContextTypes is specified but there is no getChildContext() method ' +
                    'on the instance. You can either define getChildContext() on %s or remove ' +
                    'childContextTypes from it.',
                  componentName,
                  componentName,
                );
              }
            }
            return parentContext;
          }
        
          let childContext;
          if (__DEV__) {
            ReactCurrentFiber.setCurrentPhase('getChildContext');
          }
          startPhaseTimer(fiber, 'getChildContext');
          childContext = instance.getChildContext(); // 执行这个 提供的api, 获取数据
          stopPhaseTimer();
          if (__DEV__) {
            ReactCurrentFiber.setCurrentPhase(null);
          }
          for (let contextKey in childContext) {
            invariant(
              contextKey in childContextTypes,
              '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
              getComponentName(type) || 'Unknown',
              contextKey,
            );
          }
          // 忽略
          if (__DEV__) {
            const name = getComponentName(type) || 'Unknown';
            checkPropTypes(
              childContextTypes,
              childContext,
              'child context',
              name,
              // In practice, there is one case in which we won't get a stack. It's when
              // somebody calls unstable_renderSubtreeIntoContainer() and we process
              // context from the parent component instance. The stack will be missing
              // because it's outside of the reconciliation, and so the pointer has not
              // been set. This is rare and doesn't matter. We'll also remove that API.
              ReactCurrentFiber.getCurrentFiberStackInDev,
            );
          }
          // 最终是两者 merge
          return {...parentContext, ...childContext};
        }
        
        • 其实,这个 processChildContext 非常简单,获取 context,合并 context
        • 这里的 parentContext 是传入的 previousContext, 这个是上面调用 pushContextProvider 时设置的全局变量 contextStackCursor.current
        • 也就是 父组件中 提供的 context 对象,最终都是为了合并
  • 总结来说,父子孙三个组件,在更新子组件的时候,先去push了一个它之前存在的这个属性

  • 因为我们不知道这个组件它是否要更新,不管它是否要更新,都要先都要执行 push 的一个操作

  • 所以,先 push 一个老的值进去再说, 然后到后面,如果发现这个组件它是要更新的,就调用这个 invalidateContextProvider 方法

  • 调用了这个方法之后, 根据传进来的 didChange,如果是 true 表示要更新,要重新去计算一个新的合并过的这个context, 即 mergedContext 给它推入到栈里面

  • 对于子组件来说,它的所有子树所获取到的context肯定是经过子组件,和上层的父组件合并的 context 了, 也就是 contextStackCursor 这里

  • 同时对于 didPerformWorkStackCursor 来说,因为 didChange 是 true,它的 current 肯定也是 true

  • 如果 didChange 是 false,这个时候不需要改变 contextStackCursor

    • 因为 push 的本来就是上面的那个值,也就是上一次计算出来的这个值
    • 就是保存在 __reactInternalMemoizedMergedChildContext 这上面的值
    • 因为它本身没有变化,不需要去改动它,而对于 didPerformWorkStackCursor 来说,需要去改变它
    • didChange 变成false,我没有更新,不能继续存之前的那个值, 因为之前的那个值可能是更新过了,它可能是 true
    • 我这次发现这个组件是不需要更新的, 要把它改成 false
    • 在一开始,这个方法就是叫做 hasContextChaned 的这个方法,它是用来判断这个组件
    • 是否可以跳过更新的一个非常关键的一个值, 如果它返回的一直是 true
    • 会导致我们每一个组件都需要去更新,而最终导致整个性能变得非常的差
    • 所以这就是 didPerformWorkStackCursor 它的一个作用
  • 老的 context api当中的 push 操作是比较复杂的,要进行一个 context 的合并这么一个过程

  • 到这里为止,将context的合并,并让它入栈

  • 注意,还有一种情况是这样的,父子孙三层组件,有一个子组件没有儿子组件

  • 也就是有多个子组件,其中有的子组件没有下层组件,这时候这类子组件拿到的是父组件原来的,而非合并过的

  • 看下具体的代码处理, 比如说我们随便挑一个 class component,更新过程当中调用的方法, 如 updateClassInstance 这个方法

    // Invokes the update life-cycles and returns false if it shouldn't rerender.
    function updateClassInstance(
      current: Fiber,
      workInProgress: Fiber,
      ctor: any,
      newProps: any,
      renderExpirationTime: ExpirationTime,
    ): boolean {
      const instance = workInProgress.stateNode;
    
      // ... 跳过很多代码
    
      const contextType = ctor.contextType; // 注意这里
      let nextContext;
      if (typeof contextType === 'object' && contextType !== null) {
        nextContext = readContext(contextType);
      } else {
        // 注意这个 else, 这个是重点
        const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
        nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
      }
    
      // ... 跳过很多代码
    
      return shouldUpdate;
    }
    
  • 这里有一个在 ctor.contextType 在 classComponent 上面去读取这个属性

  • 注意,这里 contextTypecontextTypes 的区别

    • 后者在应用中,我们自己的代码里用于设定接收上层属性的
    • 前者就是要去读取某一个新的 contextAPI 它的 provider 上面提供的属性
    • 也就是说可以这么设置 Child.contextType = Consumer
    • 两者共同存在,则后者老的失效
    • 也就是说,如果 孙组件使用了 新的 contextType 这个api, 后面同时定义的 contextTypes 相关的会失效
  • 对于react来说,它即将把所有context相关的东西呢都放在新的contextAPI里面

    • 所以如果我们优先使用了 contextType 这种新的 context API 的使用方式
    • 就直接默认只使用新的 context
    • 而只有在没有使用这个新的 context API 的时候才会去使用老的 context API
    • 这个时候我们就会去调用一个叫做 getUnmaskedContext(workInProgress, ctor, true)
    • 这个方法来去读取用在这一个组件上面它所对应的context属性
      function getUnmaskedContext(
        workInProgress: Fiber,
        Component: Function,
        didPushOwnContextIfProvider: boolean,
      ): Object {
        if (didPushOwnContextIfProvider && isContextProvider(Component)) {
          // If the fiber is a context provider itself, when we read its context
          // we may have already pushed its own child context on the stack. A context
          // provider should not "see" its own child context. Therefore we read the
          // previous (parent) context instead for a context provider.
          return previousContext;
        }
        return contextStackCursor.current;
      }
      
      • didPushOwnContextIfProvider 传进来的时候是 true
      • 代表是否已经 push 了自己的 contextProvider,对于我们自己是一个 contextProvider 的一个情况
      • 在调用update之前,那么肯定是已经调用过push了, 如果提供了 context,这个值就是true
      • 而后面还需要判断它是否是一个 contextProvider,如果这两个条件都符合,返回的是 previousContext
      • 这个 previousContext 就是之前在调用 push 操作的时候,即 pushContextProvider,给它赋值的这个值
      • 就比如说在更新 子组件 的过程中,执行 updateClassComponent
      • 先 push 了自己的 contextProvider, 这个时候赋值给了 previousContext
      • 它等于 push 之前的那个context,就是 父组件提供的 context
      • 对于 子组件 更新的过程当中使用的 context,肯定不能使用自己提供的这个 value
      • 子组件提供的value是给自己子树,也就是孙组件及之后使用的
      • 子组件要用context的话,应该是去读取父组件提供的 context
      • 子组件要去获取父组件提供的context,要去调用 getUnmaskedContext 这个方法
      • 而如果组件同时是一个provider,那它肯定已经push过了,所以组件必须返回 previousContext 才行
      • 同样,如果组件不是一个 contextprovider,当然没有执行过push,只需要执行当前这个cursor即可
    • 进入 getMaskedContext
      function getMaskedContext(
        workInProgress: Fiber,
        unmaskedContext: Object,
      ): Object {
        const type = workInProgress.type;
        const contextTypes = type.contextTypes;
        if (!contextTypes) {
          return emptyContextObject;
        }
      
        // Avoid recreating masked context unless unmasked context has changed.
        // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
        // This may trigger infinite loops if componentWillReceiveProps calls setState.
        const instance = workInProgress.stateNode;
        if (
          instance &&
          instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
        ) {
          return instance.__reactInternalMemoizedMaskedChildContext;
        }
      
        // 注意,这里是核心
        const context = {};
        for (let key in contextTypes) {
          context[key] = unmaskedContext[key];
        }
      
        if (__DEV__) {
          const name = getComponentName(type) || 'Unknown';
          checkPropTypes(
            contextTypes,
            context,
            'context',
            name,
            ReactCurrentFiber.getCurrentFiberStackInDev,
          );
        }
      
        // Cache unmasked context so we can avoid recreating masked context unless necessary.
        // Context is created before the class component is instantiated so check for instance.
        if (instance) {
          cacheContext(workInProgress, unmaskedContext, context);
        }
      
        return context;
      }
      
      • 这个方法是在组件的父组件中所有合并过的 context 中获得当前组件需要读取的属性
      • 也就是说当前组件需要什么指定什么,context中就返回什么
  • 以上,就是对于一个 Class Component,要使用老的contextAPI, 如何去提供这个context以及它如何去获取这个context的一个过程

  • 以上是 push 操作,那什么时候才会 pop 呢?

  • completeUnitOfWork 的时候,在 packages/react-reconciler/src/ReactFiberCompleteWork.js#L540

    function completeWork(
      current: Fiber | null,
      workInProgress: Fiber,
      renderExpirationTime: ExpirationTime,
    ): Fiber | null {
      const newProps = workInProgress.pendingProps;
      // ... 跳过很多代码
      switch (workInProgress.tag) {
        // ... 跳过很多代码
        case ClassComponent: {
          const Component = workInProgress.type;
          if (isLegacyContextProvider(Component)) {
            popLegacyContext(workInProgress);
          }
          break;
        }
        // ... 跳过很多代码
      }
      // ... 跳过很多代码
    }
    
    • 对于 classComponent,如果它是一个 provider,它必须要 popLegacyContext 别名是 popContext
      // packages/react-reconciler/src/ReactFiberContext.js#L124
      function popContext(fiber: Fiber): void {
        pop(didPerformWorkStackCursor, fiber);
        pop(contextStackCursor, fiber);
      }
      
      • 就把两个 cursor 给它 pop 一下
    • 在这里回到之前的push进行一下对比
      // packages/react-reconciler/src/ReactFiberContext.js#L215
      function pushContextProvider(workInProgress: Fiber): boolean {
        // ... 跳过很多代码
        push(contextStackCursor, memoizedMergedChildContext, workInProgress);
        push(
          didPerformWorkStackCursor,
          didPerformWorkStackCursor.current,
          workInProgress,
        );
      
        return true;
      }
      
      • 这里先push的是 contextStackCursor 再push的是 didPerformWorkStackCursor
    • 回到pop, 先pop的是 didPerformWorkStackCursor 再pop的是 contextStackCursor
    • 这就是我之前说过的,push 是按哪个顺序, pop的时候,必须要反过来去做
    • 这样的话,在 context-stack 中的 valueStack 里面存储的值对应的cursor的位置才是能真正对应起来
    • 对于在 completeWork 里面,我们只需要去执行 popLegacyContext 这个操作就可以了
  • 还是用之前的 Fiber 树来举例子

  • 假设整个树形结构下面的每一个节点,它都是一个classcomponent
  • 这个时候我们更新,然后 App 和 这个 div,它都提供了 childContextTypes
  • 这个时候对于 input 这个 classcomponent,它要更新的过程中肯定要获取
  • 这个App和和div提供的context合并之后的那个对象,对App更新的过程中,它调用了push
  • 然后在div执行更新的时候,它先去获取App push 的那个 context 作为它更新的时候要用的那个context
  • 它自己也要去push一个context,push的时候要跟App提供的context进行一个合并
  • 这个时候游标已经到了第二个context了, 第一个context是App提供的, 第二个是App和和div合并的
  • 这两个值都是在stack栈里面有存着的,只不过现在的 contextStackCursor 这个游标
  • 指向的是div它合并过之后的那个context,这个context是提供给Input渲染更新的时候,它要获取的那个值
  • 就这么一层一层下来之后,到最后更新 input 这个节点的时候,它拿到的context是上面这三个组件,它合并过的 context
  • 对于List的更新,它不需要Input合并进去,它只需要App和div它们合并之后的那个 context 对象
  • 这个就是我们在 completeUnitOfWork 的时候,要去pop这个stack
    • 在input节点执行 completeUnitOfWork 的时候
      • 要 pop input节点及以上提供的context合并之后的一个值
      • 对应 cursor 指向 App, div, Input 合并后的 context
    • 在Input节点执行 completeUnitOfWork 的时候
      • 要 pop Input节点及以上提供的context合并之后的一个值
      • 对应 cursor 指向 App 和 div 合并后的 context
    • 在Input发现sibling节点 List,要对这个兄弟节点执行 beginWork,也就是更新的流程
    • 这个时候 List 拿到的这个 context,也就是 App 和 div合并之后的节点
      • 所以这就是在 beginWork 的时候,要对 classcomponent 进行一个push的操作
      • 等到要去 completeUnitOfWork 的时候,执行到每个节点,要执行对应的pop
      • 所以这个时候就可以对节点 push 和 pop 的位置可以一一对应起来
      • 这样的话就不会造成整个stack里面的这个顺序混淆
  • 这就是对于 childContextTypes 这种context提供的方式的一个使用的过程
  • 这个 API 最终被删除了,因为前面谈到的性能问题,以上是它的整体原理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1421080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是Vue Vue入门案例

一、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 构建用户界面 的 渐进式 框架 Vue2官网&#xff1a;Vue.js 1.什么是构建用户界面 基于数据渲染出用户可以看到的界面 2.什么是渐进式 所谓渐进式就是循序渐进&#xff0c;不一定非得把V…

leetcode hot100分发饼干

在本题中&#xff0c;我们需要采用贪心算法。考虑局部最优解&#xff0c;然后再考虑全局最优解。 比如&#xff0c;在本题中&#xff0c;我们先把胃口和饼干排序&#xff0c;排序之后我们可以采用用大饼干来喂大胃口的孩子&#xff0c;如果此时能满足&#xff0c;那么计数加一…

Linux实验记录:使用LVM(逻辑卷管理器)

前言&#xff1a; 本文是一篇关于Linux系统初学者的实验记录。 参考书籍&#xff1a;《Linux就该这么学》 实验环境&#xff1a; VmwareWorkStation 17——虚拟机软件 RedHatEnterpriseLinux[RHEL]8——红帽操作系统 备注&#xff1a; 硬盘分好区或者部署为RAID磁盘阵列…

[嵌入式软件][入门篇][仿真平台] STM32CubeMX的搭建

文章目录 一、简介二、STM32CubeMX的使用(1) 新建文件&#xff0c;芯片选型(2) sys设置和RCC设置(3) 配置时钟(4) 生成代码 三、仿真平台的使用 一、简介 STM32CubeMX是一种图形工具&#xff0c;通过分步过程可以非常轻松地配置STM32微控制器和微处理器&#xff0c;生成相应的初…

13.前端--CSS-盒子模型

1.盒子模型的组成 CSS 盒子模型本质上是一个盒子&#xff0c;封装周围的 HTML 元素&#xff0c;它包括&#xff1a;边框、外边距、内边距、和 实际内容 2.边框&#xff08;border&#xff09; 2.1 边框的使用 1、border设置元素的边框。边框有三部分组成:边框宽度(粗细) 边框…

英特尔正式发布OpenVINO™ 2023.3版本

2024年1月24日&#xff0c;英特尔正式发布了OpenVINO™ 2023.3版本&#xff08;Release Notes for Intel Distribution of OpenVINO Toolkit 2023.3&#xff09;。OpenVINO™是英特尔针对自家硬件平台开发的一套深度学习工具库&#xff0c;包含推断库&#xff0c;模型优化等等一…

软件个性化选型:制造企业如何选择适合自身的工单管理系统-亿发

企业制造业是实体经济中非常重要和基础的组成部分&#xff0c;直接关系到国家经济的血脉。然而&#xff0c;传统制造业在生产与管理上所采用的老一套方法和经验已不再适应当下的发展需求。信息化、数字化和智能化被视为制造企业的必然趋势。要想在竞争激烈的市场中永立潮头&…

基于数字签名技术的挑战/响应式认证方式

挑战/响应式认证方式简便灵活&#xff0c;实现起来也比较容易。当网络需要验证用户身份时&#xff0c;客户端向服务器提出登录请求&#xff1b;当服务器接收到客户端的验证请求时&#xff0c;服务器端向客户端发送一个随机数&#xff0c;这就是这种认证方式的“冲击&#xff08…

代码随想录刷题笔记-Day15

1. 完全二叉树的的节点个数 222. 完全二叉树的节点个数https://leetcode.cn/problems/count-complete-tree-nodes/ 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没…

物联网操作系统-Alios Things

阅读引言&#xff1a; 本篇文章我想给大家分享一下Alios Things这款物联网操作系统的一些相关知识&#xff0c; 并且&#xff0c; 在文章的最后会给出我自己写的&#xff0c;非常详细的学习笔记&#xff0c; 学习笔记的思路清晰&#xff0c; 物联网操作系统的核心知识点都有涉…

vs 撤销本地 commit 并保留更改

没想到特别好的办法&#xff0c;我想的是用 vs 打开 git 命令行工具 然后通过 git 命令来撤销提交&#xff0c;尝试之前建议先建个分支实验&#xff0c;以免丢失代码&#xff0c; git 操作见 git 合并多个 commit / 修改上一次 commit

Redis -- 背景知识

目录 特性 为啥Redis快? 应用场景 Redis不能做什么&#xff1f; Redis是在内存中存储数据的一个中间件&#xff0c;用作为数据库&#xff0c;也可以用作为缓存&#xff0c;在分布式中有很高的威望。 特性 In-memory data structures&#xff1a;在内存中存储数据key-val…

MySQL原理(二)存储引擎(3)InnoDB

目录 一、概况&#xff1a; 1、介绍&#xff1a; 2、特点&#xff1a; 二、体系架构 1、后台线程 2、内存池&#xff08;缓冲池&#xff09; 三、物理结构 1、数据文件&#xff08;表数据和索引数据&#xff09; 1.1、作用&#xff1a; 1.2、共享表空间与独立表空间 …

vue异步跟随表单上传图片 单张or多张

下面是获取的后台照片展示 &#xff0c;我这里限制了图片数量 limit 为限制的图片数量多少 dialogVisibles 为控制是否有图片 contractPicUrl为展示的图片后台url 若有多张图片 需要使用for循环方法 <el-form-item label"合同照片" prop"contractPic" …

数字化时代,元宇宙展厅越来越受欢迎,知道原因吗?

在数字化时代&#xff0c;元宇宙展厅越来越受欢迎。元宇宙展厅可以针对公司的发展历程以及荣誉成就&#xff0c;打造设计一个虚拟的展示空间&#xff0c;让参观者在虚拟环境中参观和交互&#xff0c;并步进式漫游公司介绍、业务板块、经典案例以及荣誉资质等模块&#xff0c;让…

如何在Shopee印尼站点进行有效的选品策略?

在Shopee印尼站点进行选品是卖家们提高产品销售业绩的重要一环。然而&#xff0c;要在竞争激烈的市场中脱颖而出&#xff0c;卖家需要制定一套有效的选品策略。本文将介绍一些关键的策略&#xff0c;帮助卖家们在Shopee印尼站点上实现成功的选品。 先给大家推荐一款shopee知虾…

tritonserver学习之六:自定义c++、python custom backend实践

tritonserver学习之一&#xff1a;triton使用流程 tritonserver学习之二&#xff1a;tritonserver编译 tritonserver学习之三&#xff1a;tritonserver运行流程 tritonserver学习之四&#xff1a;命令行解析 tritonserver学习之五&#xff1a;backend实现机制 1、环境准备…

网络隔离场景下访问 Pod 网络

接着上文 VPC网络架构下的网络上数据采集 介绍 考虑一个监控系统&#xff0c;它的数据采集 Agent 是以 daemonset 形式运行在物理机上的&#xff0c;它需要采集 Pod 的各种监控信息。现在很流行的一个监控信息是通过 Prometheus 提供指标信息。 一般来说&#xff0c;daemonset …

NoMachine局域网桌面远程控制管理工具软件,下载配置教程

近期入手了一台雷神MIX Mini主机&#xff0c;用于开发使用&#xff0c;但是因为主机没有显示器和键盘等输入设备&#xff0c;如果需要对Mini主机进行控制&#xff0c;则要频繁插入显示器和键盘&#xff0c;操作起来相当的不方便。 在体验了一些常见的桌面控制软件&#xff0c;…

如何使用docker快速安装Plik并实现固定公网地址远程访问

文章目录 推荐1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点…